)]}' {"version": 3, "sources": ["/web/static/tests/mobile/core/action_swiper_tests.js", "/web/static/tests/mobile/helpers.js", "/web/static/tests/mobile/search/control_panel_tests.js", "/web/static/tests/mobile/search/search_panel_tests.js", "/web/static/tests/mobile/views/calendar_view_tests.js", "/web/static/tests/mobile/views/fields/many2many_tags_field_tests.js", "/web/static/tests/mobile/views/fields/many2one_barcode_field_tests.js", "/web/static/tests/mobile/views/fields/statusbar_field_tests.js", "/web/static/tests/mobile/views/form_view_tests.js", "/web/static/tests/mobile/views/list_view_tests.js", "/web/static/tests/mobile/views/view_dialog/select_create_dialog_tests.js", "/web/static/tests/mobile/views/widgets/signature_tests.js", "/web/static/tests/mobile/webclient/burger_menu/burger_menu_tests.js", "/web/static/tests/mobile/webclient/burger_menu/burger_user_menu_tests.js", "/web/static/tests/mobile/webclient/settings_form_view_tests.js", "/web/static/tests/legacy/fields/basic_fields_mobile_tests.js", "/web/static/tests/legacy/fields/relational_fields_mobile_tests.js", "/web/static/tests/legacy/components/dropdown_menu_mobile_tests.js", "/bus/static/tests/helpers/mock_python_environment.js", "/bus/static/tests/helpers/mock_server.js", "/bus/static/tests/helpers/mock_server/models/ir_websocket.js", "/bus/static/tests/helpers/mock_services.js", "/bus/static/tests/helpers/mock_websocket.js", "/bus/static/tests/helpers/model_definitions_helpers.js", "/bus/static/tests/helpers/model_definitions_setup.js", "/bus/static/tests/helpers/test_constants.js", "/bus/static/tests/helpers/view_definitions_setup.js", "/mail/static/tests/qunit_mobile_suite_tests/components/discuss_mobile_mailbox_selection_tests.js", "/barcodes/static/tests/mobile/barcode_mobile_tests.js", "/web_enterprise/static/tests/views/disable_patch.js", "/web_enterprise/static/tests/mobile/burger_menu_tests.js", "/web_enterprise/static/tests/mobile/pivot_view_tests.js", "/web_enterprise/static/tests/mobile/webclient_mobile_tests.js", "/web_enterprise/static/tests/legacy/action_manager_mobile_tests.js", "/web_enterprise/static/tests/legacy/control_panel_mobile_tests.js", "/web_enterprise/static/tests/legacy/form_mobile_tests.js", "/web_enterprise/static/tests/legacy/relational_fields_mobile_tests.js", "/web_enterprise/static/tests/legacy/views/basic/basic_render_mobile_tests.js", "/web_enterprise/static/tests/legacy/views/kanban_mobile_tests.js", "/web_enterprise/static/tests/legacy/views/list_mobile_tests.js", "/web_enterprise/static/tests/legacy/components/action_menus_mobile_tests.js", "/web_enterprise/static/tests/legacy/barcodes_mobile_tests.js", "/documents/static/tests/documents_test_utils.js", "/documents/static/tests/documents_kanban_mobile_tests.js", "/web_mobile/static/tests/user_menu_mobile_tests.js", "/web_mobile/static/tests/web_mobile_tests.js", "/web_grid/static/tests/grid_mobile_tests.js", "/web_grid/static/tests/mock_server.js", "/web_map/static/tests/map_view/map_view_mobile_tests.js", "/web_map/static/lib/leaflet/leaflet.js", "/spreadsheet_edition/static/tests/utils/mock_server.js", "/web_gantt/static/tests/gantt_mobile_tests.js", "/google_calendar/static/tests/google_calendar_mock_server.js", "/hr_mobile/static/tests/language_mobile_tests.js", "/spreadsheet_dashboard/static/tests/mobile/mobile_dashboard_action_test.js", "/spreadsheet_dashboard/static/tests/utils/dashboard_action.js", "/spreadsheet_dashboard/static/tests/utils/data.js", "/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js", "/spreadsheet_dashboard_documents/static/src/bundle/documents_spreadsheet_action.js", "/spreadsheet_account/static/src/accounting_datasource.js", "/spreadsheet_account/static/src/accounting_functions.js", "/spreadsheet_account/static/src/index.js", "/spreadsheet_account/static/src/plugins/accounting_plugin.js", "/spreadsheet_account/static/src/utils.js", "/spreadsheet/static/src/actions/spreadsheet_download_action.js", "/spreadsheet/static/src/chart/data_source/chart_data_source.js", "/spreadsheet/static/src/chart/index.js", "/spreadsheet/static/src/chart/odoo_chart/odoo_bar_chart.js", "/spreadsheet/static/src/chart/odoo_chart/odoo_chart.js", "/spreadsheet/static/src/chart/odoo_chart/odoo_line_chart.js", "/spreadsheet/static/src/chart/odoo_chart/odoo_pie_chart.js", "/spreadsheet/static/src/chart/odoo_menu/chart_figure.js", "/spreadsheet/static/src/chart/plugins/chart_odoo_menu_plugin.js", "/spreadsheet/static/src/chart/plugins/odoo_chart_core_plugin.js", "/spreadsheet/static/src/chart/plugins/odoo_chart_ui_plugin.js", "/spreadsheet/static/src/chart/plugins/operational_transform.js", "/spreadsheet/static/src/currency/currency_data_source.js", "/spreadsheet/static/src/currency/formulas.js", "/spreadsheet/static/src/currency/plugins/currency.js", "/spreadsheet/static/src/data_sources/data_source.js", "/spreadsheet/static/src/data_sources/data_sources.js", "/spreadsheet/static/src/data_sources/display_name_repository.js", "/spreadsheet/static/src/data_sources/labels_repository.js", "/spreadsheet/static/src/data_sources/metadata_repository.js", "/spreadsheet/static/src/data_sources/odoo_views_data_source.js", "/spreadsheet/static/src/data_sources/server_data.js", "/spreadsheet/static/src/global_filters/components/filter_date_value/filter_date_value.js", "/spreadsheet/static/src/global_filters/components/filter_value/filter_value.js", "/spreadsheet/static/src/global_filters/components/records_selector/records_selector.js", "/spreadsheet/static/src/global_filters/components/year_picker.js", "/spreadsheet/static/src/global_filters/helpers.js", "/spreadsheet/static/src/global_filters/index.js", "/spreadsheet/static/src/global_filters/plugins/global_filters_core_plugin.js", "/spreadsheet/static/src/global_filters/plugins/global_filters_ui_plugin.js", "/spreadsheet/static/src/helpers/constants.js", "/spreadsheet/static/src/helpers/helpers.js", "/spreadsheet/static/src/helpers/odoo_functions_helpers.js", "/spreadsheet/static/src/index.js", "/spreadsheet/static/src/ir_ui_menu/index.js", "/spreadsheet/static/src/ir_ui_menu/ir_ui_menu_plugin.js", "/spreadsheet/static/src/ir_ui_menu/odoo_menu_link_cell.js", "/spreadsheet/static/src/list/index.js", "/spreadsheet/static/src/list/list_actions.js", "/spreadsheet/static/src/list/list_data_source.js", "/spreadsheet/static/src/list/list_functions.js", "/spreadsheet/static/src/list/list_helpers.js", "/spreadsheet/static/src/list/plugins/list_core_plugin.js", "/spreadsheet/static/src/list/plugins/list_ui_plugin.js", "/spreadsheet/static/src/o_spreadsheet/cancelled_reason.js", "/spreadsheet/static/src/o_spreadsheet/errors.js", "/spreadsheet/static/src/o_spreadsheet/migration.js", "/spreadsheet/static/src/o_spreadsheet/o_spreadsheet_extended.js", "/spreadsheet/static/src/o_spreadsheet/translation.js", "/spreadsheet/static/src/pivot/index.js", "/spreadsheet/static/src/pivot/pivot_actions.js", "/spreadsheet/static/src/pivot/pivot_data_source.js", "/spreadsheet/static/src/pivot/pivot_functions.js", "/spreadsheet/static/src/pivot/pivot_helpers.js", "/spreadsheet/static/src/pivot/pivot_model.js", "/spreadsheet/static/src/pivot/pivot_table.js", "/spreadsheet/static/src/pivot/plugins/pivot_core_plugin.js", "/spreadsheet/static/src/pivot/plugins/pivot_ui_plugin.js", "/spreadsheet_edition/static/src/bundle/actions/abstract_spreadsheet_action.js", "/spreadsheet_edition/static/src/bundle/actions/control_panel/spreadsheet_control_panel.js", "/spreadsheet_edition/static/src/bundle/actions/control_panel/spreadsheet_name.js", "/spreadsheet_edition/static/src/bundle/actions/spreadsheet_component.js", "/spreadsheet_edition/static/src/bundle/chart/odoo_chart_insertion.js", "/spreadsheet_edition/static/src/bundle/chart/odoo_menu/chart_panel.js", "/spreadsheet_edition/static/src/bundle/chart/side_panels/common/config_panel.js", "/spreadsheet_edition/static/src/bundle/chart/side_panels/index.js", "/spreadsheet_edition/static/src/bundle/chart/side_panels/main_side_panel_chart.js", "/spreadsheet_edition/static/src/bundle/chart/side_panels/odoo_bar/odoo_bar_config_panel.js", "/spreadsheet_edition/static/src/bundle/chart/side_panels/odoo_line/odoo_line_config_panel.js", "/spreadsheet_edition/static/src/bundle/global_filters/components/filter_editor/date_filter_editor_side_panel.js", "/spreadsheet_edition/static/src/bundle/global_filters/components/filter_editor/filter_editor_field_matching.js", "/spreadsheet_edition/static/src/bundle/global_filters/components/filter_editor/filter_editor_label.js", "/spreadsheet_edition/static/src/bundle/global_filters/components/filter_editor/filter_editor_side_panel.js", "/spreadsheet_edition/static/src/bundle/global_filters/components/filter_editor/relation_filter_editor_side_panel.js", "/spreadsheet_edition/static/src/bundle/global_filters/components/filter_editor/text_filter_editor_side_panel.js", "/spreadsheet_edition/static/src/bundle/global_filters/components/filter_field_offset.js", "/spreadsheet_edition/static/src/bundle/global_filters/components/model_field_selector/spreadsheet_model_field_selector.js", "/spreadsheet_edition/static/src/bundle/global_filters/components/model_field_selector/spreadsheet_model_field_selector_popover.js", "/spreadsheet_edition/static/src/bundle/global_filters/filter_component.js", "/spreadsheet_edition/static/src/bundle/global_filters/global_filters_side_panel.js", "/spreadsheet_edition/static/src/bundle/global_filters/index.js", "/spreadsheet_edition/static/src/bundle/global_filters/operational_transform.js", "/spreadsheet_edition/static/src/bundle/helpers.js", "/spreadsheet_edition/static/src/bundle/image/record_file_store.js", "/spreadsheet_edition/static/src/bundle/ir_ui_menu/index.js", "/spreadsheet_edition/static/src/bundle/list/autofill.js", "/spreadsheet_edition/static/src/bundle/list/index.js", "/spreadsheet_edition/static/src/bundle/list/list_actions.js", "/spreadsheet_edition/static/src/bundle/list/list_init_callback.js", "/spreadsheet_edition/static/src/bundle/list/operational_transform.js", "/spreadsheet_edition/static/src/bundle/list/plugins/list_autofill_plugin.js", "/spreadsheet_edition/static/src/bundle/list/side_panels/listing_all_side_panel.js", "/spreadsheet_edition/static/src/bundle/list/side_panels/listing_details_side_panel.js", "/spreadsheet_edition/static/src/bundle/o_spreadsheet/collaborative/spreadsheet_collaborative_channel.js", "/spreadsheet_edition/static/src/bundle/o_spreadsheet/collaborative/spreadsheet_collaborative_service.js", "/spreadsheet_edition/static/src/bundle/o_spreadsheet/editable_name/editable_name.js", "/spreadsheet_edition/static/src/bundle/o_spreadsheet/menu_item_registry.js", "/spreadsheet_edition/static/src/bundle/pivot/autofill.js", "/spreadsheet_edition/static/src/bundle/pivot/index.js", "/spreadsheet_edition/static/src/bundle/pivot/operational_transform.js", "/spreadsheet_edition/static/src/bundle/pivot/pivot_actions.js", "/spreadsheet_edition/static/src/bundle/pivot/pivot_init_callback.js", "/spreadsheet_edition/static/src/bundle/pivot/plugins/pivot_autofill_plugin.js", "/spreadsheet_edition/static/src/bundle/pivot/side_panels/pivot_details_side_panel.js", "/spreadsheet_edition/static/src/bundle/pivot/side_panels/pivot_list_side_panel.js", "/spreadsheet_edition/static/src/bundle/pivot/spreadsheet_pivot_dialog.js", "/spreadsheet_edition/static/src/bundle/pivot/spreadsheet_pivot_dialog_table.js", "/documents_spreadsheet/static/src/bundle/actions/spreadsheet_action.js", "/documents_spreadsheet/static/src/bundle/actions/spreadsheet_template/spreadsheet_template_action.js", "/documents_spreadsheet/static/src/bundle/components/control_panel/spreadsheet_control_panel.js", "/documents_spreadsheet/static/src/bundle/components/spreadsheet_component.js", "/documents_spreadsheet/static/src/bundle/helpers.js", "/documents_spreadsheet/static/src/bundle/pivot/pivot_data_source.js", "/documents_spreadsheet/static/src/bundle/pivot/pivot_model.js", "/documents_spreadsheet/static/src/bundle/pivot/plugin/pivot_template_plugin.js", "/spreadsheet_dashboard/static/src/bundle/dashboard_action/dashboard_action.js", "/spreadsheet_dashboard/static/src/bundle/dashboard_action/dashboard_loader.js", "/spreadsheet_dashboard/static/src/bundle/dashboard_action/mobile_figure_container/mobile_figure_container.js", "/spreadsheet_dashboard/static/src/bundle/dashboard_action/mobile_search_panel/mobile_search_panel.js", "/spreadsheet_dashboard/static/src/bundle/list/clickable_cell.js", "/spreadsheet_dashboard/static/src/bundle/pivot/clickable_cell.js", "/spreadsheet_dashboard_edition/static/src/bundle/action/dashboard_action.js", "/spreadsheet_dashboard_edition/static/src/bundle/action/dashboard_edit_action.js", "/spreadsheet_dashboard_edition/static/tests/mock_server.js"], "mappings": "AAAA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACl2BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5HA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9JA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5YA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjMA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjNA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACleA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9OA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5IA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACnOA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AClEA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACpEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5NA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACfA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACbA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxGA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACzGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACNA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzLA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChIA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACrPA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACzZA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AClfA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACzEA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACxNA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACrOA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AClFA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AChKA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxVA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChvBA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC7HA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AC/NA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnGA;;;;;AAAA;AACA;AACA;AACA;AACA;ACJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClBA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACpIA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzDA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACt61CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzSA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrIA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7HA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClQA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1MA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9HA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxTA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9PA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/cA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1IA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACnDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvLA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5eA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1MA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACfA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACZA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrOA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACfA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AClOA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1pBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrLA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChjBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtVA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9IA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACpDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzYA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACfA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACfA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7IA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACjDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3QA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/KA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACbA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AC9DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACXA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACzCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AChCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvEA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1GA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtFA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/GA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzIA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACzCA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7EA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC7DA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACroBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACxGA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACzXA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACTA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9JA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9FA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACvBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrDA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC5BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACdA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/FA;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACxVA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACnJA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC/NA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC3CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC1CA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACZA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AC9BA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACrBA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;ACtHA;;;;;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA", "sourcesContent": ["/** @odoo-module **/\n\nimport { ActionSwiper } from \"@web/core/action_swiper/action_swiper\";\nimport { registry } from \"@web/core/registry\";\nimport { makeFakeLocalizationService } from \"@web/../tests/helpers/mock_services\";\nimport { makeTestEnv } from \"@web/../tests/helpers/mock_env\";\n\nimport {\n mount,\n nextTick,\n triggerEvent,\n getFixture,\n mockTimeout,\n} from \"@web/../tests/helpers/utils\";\n\nimport { Component, xml } from \"@odoo/owl\";\nconst serviceRegistry = registry.category(\"services\");\n\nlet env;\nlet target;\n\nQUnit.module(\"ActionSwiper\", ({ beforeEach }) => {\n beforeEach(async () => {\n env = await makeTestEnv();\n target = getFixture();\n });\n\n // Tests marked as [REQUIRE TOUCHEVENT] will fail on browsers that don't support\n // TouchEvent by default. It might be an option to activate on some browser.\n\n QUnit.test(\"render only its target if no props is given\", async (assert) => {\n class Parent extends Component {}\n Parent.components = { ActionSwiper };\n Parent.template = xml`\n
\n \n
\n \n
\n `;\n await mount(Parent, target, { env });\n assert.containsNone(target, \"div.o_actionswiper\");\n assert.containsOnce(target, \"div.target-component\");\n });\n\n QUnit.test(\"only render the necessary divs\", async (assert) => {\n await mount(ActionSwiper, target, {\n env,\n props: {\n onRightSwipe: {\n action: () => {},\n icon: \"fa-circle\",\n bgColor: \"bg-warning\",\n },\n slots: {},\n },\n });\n assert.containsOnce(target, \"div.o_actionswiper_right_swipe_area\");\n assert.containsNone(target, \"div.o_actionswiper_left_swipe_area\");\n await mount(ActionSwiper, target, {\n env,\n props: {\n onLeftSwipe: {\n action: () => {},\n icon: \"fa-circle\",\n bgColor: \"bg-warning\",\n },\n slots: {},\n },\n });\n assert.containsOnce(target, \"div.o_actionswiper_right_swipe_area\");\n assert.containsOnce(target, \"div.o_actionswiper_left_swipe_area\");\n });\n\n QUnit.test(\"render with the height of its content\", async (assert) => {\n assert.expect(2);\n class Parent extends Component {\n onRightSwipe() {\n assert.step(\"onRightSwipe\");\n }\n }\n Parent.components = { ActionSwiper };\n Parent.template = xml`\n
\n \n
This element is very high and\n the o-container element must have a scrollbar
\n
\n
\n `;\n await mount(Parent, target, { env });\n assert.ok(\n target.querySelector(\".o_actionswiper\").scrollHeight ===\n target.querySelector(\".target-component\").scrollHeight,\n \"the swiper has the height of its content\"\n );\n assert.ok(\n target.querySelector(\".o_actionswiper\").scrollHeight >\n target.querySelector(\".o_actionswiper\").clientHeight,\n \"the height of the swiper must make the parent div scrollable\"\n );\n });\n\n QUnit.test(\n \"can perform actions by swiping to the right [REQUIRE TOUCHEVENT]\",\n async (assert) => {\n assert.expect(5);\n const { execRegisteredTimeouts } = mockTimeout();\n class Parent extends Component {\n onRightSwipe() {\n assert.step(\"onRightSwipe\");\n }\n }\n Parent.components = { ActionSwiper };\n Parent.template = xml`\n
\n \n
Test
\n
\n
\n `;\n await mount(Parent, target, { env });\n const swiper = target.querySelector(\".o_actionswiper\");\n const targetContainer = target.querySelector(\".o_actionswiper_target_container\");\n await triggerEvent(target, \".o_actionswiper\", \"touchstart\", {\n touches: [\n {\n identifier: 0,\n clientX: 0,\n clientY: 0,\n target: target,\n },\n ],\n });\n await triggerEvent(target, \".o_actionswiper\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: (3 * swiper.clientWidth) / 4,\n clientY: 0,\n target: target,\n },\n ],\n });\n assert.ok(\n targetContainer.style.transform.includes(\"translateX\"),\n \"target has translateX\"\n );\n // Touch ends before the half of the distance has been reached\n await triggerEvent(target, \".o_actionswiper\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: swiper.clientWidth / 2 - 1,\n clientY: 0,\n target: target,\n },\n ],\n });\n await triggerEvent(target, \".o_actionswiper\", \"touchend\", {});\n execRegisteredTimeouts();\n await nextTick();\n assert.ok(\n !targetContainer.style.transform.includes(\"translateX\"),\n \"target does not have a translate value\"\n );\n // Touch ends once the half of the distance has been crossed\n await triggerEvent(target, \".o_actionswiper\", \"touchstart\", {\n touches: [\n {\n identifier: 0,\n clientX: swiper.clientWidth / 2,\n clientY: 0,\n target: target,\n },\n ],\n });\n await triggerEvent(target, \".o_actionswiper\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: swiper.clientWidth + 1,\n clientY: 0,\n target: target,\n },\n ],\n });\n await triggerEvent(target, \".o_actionswiper\", \"touchend\", {});\n execRegisteredTimeouts();\n await nextTick();\n // The action is performed AND the component is reset\n assert.ok(\n !targetContainer.style.transform.includes(\"translateX\"),\n \"target doesn't have translateX after action is performed\"\n );\n assert.verifySteps([\"onRightSwipe\"]);\n }\n );\n\n QUnit.test(\n \"can perform actions by swiping in both directions [REQUIRE TOUCHEVENT]\",\n async (assert) => {\n assert.expect(7);\n const { execRegisteredTimeouts } = mockTimeout();\n class Parent extends Component {\n onRightSwipe() {\n assert.step(\"onRightSwipe\");\n }\n onLeftSwipe() {\n assert.step(\"onLeftSwipe\");\n }\n }\n Parent.components = { ActionSwiper };\n Parent.template = xml`\n
\n \n
Swipe in both directions
\n
\n
\n `;\n await mount(Parent, target, { env });\n const swiper = target.querySelector(\".o_actionswiper\");\n const targetContainer = target.querySelector(\".o_actionswiper_target_container\");\n await triggerEvent(target, \".o_actionswiper\", \"touchstart\", {\n touches: [\n {\n identifier: 0,\n clientX: 0,\n clientY: 0,\n target: target,\n },\n ],\n });\n await triggerEvent(target, \".o_actionswiper\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: (3 * swiper.clientWidth) / 4,\n clientY: 0,\n target: target,\n },\n ],\n });\n assert.ok(\n targetContainer.style.transform.includes(\"translateX\"),\n \"target has translateX\"\n );\n // Touch ends before the half of the distance has been reached to the left\n await triggerEvent(target, \".o_actionswiper\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: -swiper.clientWidth / 2 + 1,\n clientY: 0,\n target: target,\n },\n ],\n });\n await triggerEvent(target, \".o_actionswiper\", \"touchend\", {});\n execRegisteredTimeouts();\n await nextTick();\n assert.ok(\n !targetContainer.style.transform.includes(\"translateX\"),\n \"target does not have a translate value\"\n );\n // Touch ends once the half of the distance has been crossed to the left\n await triggerEvent(target, \".o_actionswiper\", \"touchstart\", {\n touches: [\n {\n identifier: 0,\n clientX: swiper.clientWidth / 2,\n clientY: 0,\n target: target,\n },\n ],\n });\n await triggerEvent(target, \".o_actionswiper\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: -swiper.clientWidth - 1,\n clientY: 0,\n target: target,\n },\n ],\n });\n await triggerEvent(target, \".o_actionswiper\", \"touchend\", {});\n execRegisteredTimeouts();\n await nextTick();\n assert.verifySteps([\"onLeftSwipe\"], \"the onLeftSwipe props action has been performed\");\n // Touch ends once the half of the distance has been crossed to the right\n await triggerEvent(target, \".o_actionswiper\", \"touchstart\", {\n touches: [\n {\n identifier: 0,\n clientX: swiper.clientWidth / 2,\n clientY: 0,\n target: target,\n },\n ],\n });\n await triggerEvent(target, \".o_actionswiper\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: swiper.clientWidth + 1,\n clientY: 0,\n target: target,\n },\n ],\n });\n await triggerEvent(target, \".o_actionswiper\", \"touchend\", {});\n execRegisteredTimeouts();\n await nextTick();\n assert.ok(\n !targetContainer.style.transform.includes(\"translateX\"),\n \"target doesn't have translateX after all actions are performed\"\n );\n assert.verifySteps(\n [\"onRightSwipe\"],\n \"the onRightSwipe props action has been performed\"\n );\n }\n );\n\n QUnit.test(\n \"invert the direction of swipes when language is rtl [REQUIRE TOUCHEVENT]\",\n async (assert) => {\n assert.expect(7);\n const { execRegisteredTimeouts } = mockTimeout();\n class Parent extends Component {\n onRightSwipe() {\n assert.step(\"onRightSwipe\");\n }\n onLeftSwipe() {\n assert.step(\"onLeftSwipe\");\n }\n }\n Parent.components = { ActionSwiper };\n Parent.template = xml`\n
\n \n
Swipe in both directions
\n
\n
\n `;\n serviceRegistry.add(\"localization\", makeFakeLocalizationService({ direction: \"rtl\" }));\n await mount(Parent, target, { env });\n const swiper = target.querySelector(\".o_actionswiper\");\n const targetContainer = target.querySelector(\".o_actionswiper_target_container\");\n await triggerEvent(target, \".o_actionswiper\", \"touchstart\", {\n touches: [\n {\n identifier: 0,\n clientX: 0,\n clientY: 0,\n target: target,\n },\n ],\n });\n await triggerEvent(target, \".o_actionswiper\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: (3 * swiper.clientWidth) / 4,\n clientY: 0,\n target: target,\n },\n ],\n });\n assert.ok(\n targetContainer.style.transform.includes(\"translateX\"),\n \"target has translateX\"\n );\n // Touch ends before the half of the distance has been reached to the left\n await triggerEvent(target, \".o_actionswiper\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: -swiper.clientWidth / 2 + 1,\n clientY: 0,\n target: target,\n },\n ],\n });\n await triggerEvent(target, \".o_actionswiper\", \"touchend\", {});\n execRegisteredTimeouts();\n await nextTick();\n assert.ok(\n !targetContainer.style.transform.includes(\"translateX\"),\n \"target does not have a translate value\"\n );\n // Touch ends once the half of the distance has been crossed to the left\n await triggerEvent(target, \".o_actionswiper\", \"touchstart\", {\n touches: [\n {\n identifier: 0,\n clientX: swiper.clientWidth / 2,\n clientY: 0,\n target: target,\n },\n ],\n });\n await triggerEvent(target, \".o_actionswiper\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: -swiper.clientWidth - 1,\n clientY: 0,\n target: target,\n },\n ],\n });\n await triggerEvent(target, \".o_actionswiper\", \"touchend\", {});\n execRegisteredTimeouts();\n await nextTick();\n // In rtl languages, actions are permuted\n assert.verifySteps(\n [\"onRightSwipe\"],\n \"the onRightSwipe props action has been performed\"\n );\n await triggerEvent(target, \".o_actionswiper\", \"touchstart\", {\n touches: [\n {\n identifier: 0,\n clientX: swiper.clientWidth / 2,\n clientY: 0,\n target: target,\n },\n ],\n });\n await triggerEvent(target, \".o_actionswiper\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: swiper.clientWidth + 1,\n clientY: 0,\n target: target,\n },\n ],\n });\n await triggerEvent(target, \".o_actionswiper\", \"touchend\", {});\n execRegisteredTimeouts();\n await nextTick();\n assert.ok(\n !targetContainer.style.transform.includes(\"translateX\"),\n \"target doesn't have translateX after all actions are performed\"\n );\n // In rtl languages, actions are permuted\n assert.verifySteps([\"onLeftSwipe\"], \"the onLeftSwipe props action has been performed\");\n }\n );\n\n QUnit.test(\n \"swiping when the swiper contains scrollable areas [REQUIRE TOUCHEVENT]\",\n async (assert) => {\n assert.expect(9);\n const { execRegisteredTimeouts } = mockTimeout();\n class Parent extends Component {\n onRightSwipe() {\n assert.step(\"onRightSwipe\");\n }\n onLeftSwipe() {\n assert.step(\"onLeftSwipe\");\n }\n }\n Parent.components = { ActionSwiper };\n Parent.template = xml`\n
\n \n
\n

Test about swiping and scrolling

\n
\n

This div contains a larger element that will make it scrollable

\n

This element is so large it needs to be scrollable

\n
\n
\n
\n
\n `;\n await mount(Parent, target, { env });\n const swiper = target.querySelector(\".o_actionswiper\");\n const targetContainer = target.querySelector(\".o_actionswiper_target_container\");\n const scrollable = target.querySelector(\".large-content\");\n // The scrollable element is set as scrollable\n scrollable.scrollLeft = 100;\n await triggerEvent(target, \".o_actionswiper\", \"touchstart\", {\n touches: [\n {\n identifier: 0,\n clientX: 0,\n clientY: 0,\n target: target,\n },\n ],\n });\n await triggerEvent(target, \".o_actionswiper\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: (3 * swiper.clientWidth) / 4,\n clientY: 0,\n target: target,\n },\n ],\n });\n assert.ok(\n targetContainer.style.transform.includes(\"translateX\"),\n \"the swiper can swipe if the scrollable area is not under touch pressure\"\n );\n await triggerEvent(target, \".o_actionswiper\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: 0,\n clientY: 0,\n target: target,\n },\n ],\n });\n await triggerEvent(target, \".o_actionswiper\", \"touchend\", {});\n await triggerEvent(scrollable, \".large-text\", \"touchstart\", {\n touches: [\n {\n identifier: 0,\n clientX: scrollable.clientLeft,\n clientY:\n scrollable.getBoundingClientRect().top +\n scrollable.getBoundingClientRect().height / 2,\n target: scrollable.querySelector(\".large-text\"),\n },\n ],\n });\n await triggerEvent(scrollable, \".large-text\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: scrollable.clientWidth,\n clientY:\n scrollable.getBoundingClientRect().top +\n scrollable.getBoundingClientRect().height / 2,\n target: scrollable.querySelector(\".large-text\"),\n },\n ],\n });\n assert.ok(\n !targetContainer.style.transform.includes(\"translateX\"),\n \"the swiper has not swiped to the right because the scrollable element was scrollable to the left\"\n );\n // The scrollable element is set at its left limit\n scrollable.scrollLeft = 0;\n await triggerEvent(scrollable, \".large-text\", \"touchstart\", {\n touches: [\n {\n identifier: 0,\n clientX: scrollable.clientLeft,\n clientY:\n scrollable.getBoundingClientRect().top +\n scrollable.getBoundingClientRect().height / 2,\n target: scrollable.querySelector(\".large-text\"),\n },\n ],\n });\n await triggerEvent(scrollable, \".large-text\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: scrollable.clientWidth,\n clientY:\n scrollable.getBoundingClientRect().top +\n scrollable.getBoundingClientRect().height / 2,\n target: scrollable.querySelector(\".large-text\"),\n },\n ],\n });\n assert.ok(\n targetContainer.style.transform.includes(\"translateX\"),\n \"the swiper has swiped to the right because the scrollable element couldn't scroll anymore to the left\"\n );\n await triggerEvent(target, \".o_actionswiper\", \"touchend\", {});\n execRegisteredTimeouts();\n await nextTick();\n assert.verifySteps(\n [\"onRightSwipe\"],\n \"the onRightSwipe props action has been performed\"\n );\n await triggerEvent(scrollable, \".large-text\", \"touchstart\", {\n touches: [\n {\n identifier: 0,\n clientX: scrollable.clientWidth,\n clientY:\n scrollable.getBoundingClientRect().top +\n scrollable.getBoundingClientRect().height / 2,\n target: scrollable.querySelector(\".large-text\"),\n },\n ],\n });\n await triggerEvent(scrollable, \".large-text\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: scrollable.clientLeft,\n clientY:\n scrollable.getBoundingClientRect().top +\n scrollable.getBoundingClientRect().height / 2,\n target: scrollable.querySelector(\".large-text\"),\n },\n ],\n });\n assert.ok(\n !targetContainer.style.transform.includes(\"translateX\"),\n \"the swiper has not swiped to the left because the scrollable element was scrollable to the right\"\n );\n // The scrollable element is set at its right limit\n scrollable.scrollLeft =\n scrollable.scrollWidth - scrollable.getBoundingClientRect().right;\n await triggerEvent(scrollable, \".large-text\", \"touchstart\", {\n touches: [\n {\n identifier: 0,\n clientX: scrollable.clientWidth,\n clientY:\n scrollable.getBoundingClientRect().top +\n scrollable.getBoundingClientRect().height / 2,\n target: scrollable.querySelector(\".large-text\"),\n },\n ],\n });\n await triggerEvent(scrollable, \".large-text\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: scrollable.clientLeft,\n clientY:\n scrollable.getBoundingClientRect().top +\n scrollable.getBoundingClientRect().height / 2,\n target: scrollable.querySelector(\".large-text\"),\n },\n ],\n });\n assert.ok(\n targetContainer.style.transform.includes(\"translateX\"),\n \"the swiper has swiped to the left because the scrollable element couldn't scroll anymore to the right\"\n );\n await triggerEvent(target, \".o_actionswiper\", \"touchend\", {});\n execRegisteredTimeouts();\n await nextTick();\n assert.verifySteps([\"onLeftSwipe\"], \"the onLeftSwipe props action has been performed\");\n }\n );\n\n QUnit.test(\n \"preventing swipe on scrollable areas when language is rtl [REQUIRE TOUCHEVENT]\",\n async (assert) => {\n assert.expect(8);\n const { execRegisteredTimeouts } = mockTimeout();\n class Parent extends Component {\n onRightSwipe() {\n assert.step(\"onRightSwipe\");\n }\n onLeftSwipe() {\n assert.step(\"onLeftSwipe\");\n }\n }\n Parent.components = { ActionSwiper };\n Parent.template = xml`\n
\n \n
\n

Test about swiping and scrolling for rtl

\n
\n

elballorcs ti ekam lliw taht tnemele regral a sniatnoc vid sihT

\n

elballorcs eb ot sdeen ti egral os si tnemele sihT

\n
\n
\n \n
\n `;\n serviceRegistry.add(\"localization\", makeFakeLocalizationService({ direction: \"rtl\" }));\n await mount(Parent, target, { env });\n const targetContainer = target.querySelector(\".o_actionswiper_target_container\");\n const scrollable = target.querySelector(\".large-content\");\n // The scrollable element is set as scrollable\n scrollable.scrollLeft = 100;\n await triggerEvent(scrollable, \".large-text\", \"touchstart\", {\n touches: [\n {\n identifier: 0,\n clientX: scrollable.clientLeft,\n clientY:\n scrollable.getBoundingClientRect().top +\n scrollable.getBoundingClientRect().height / 2,\n target: scrollable.querySelector(\".large-text\"),\n },\n ],\n });\n await triggerEvent(scrollable, \".large-text\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: scrollable.clientWidth,\n clientY:\n scrollable.getBoundingClientRect().top +\n scrollable.getBoundingClientRect().height / 2,\n target: scrollable.querySelector(\".large-text\"),\n },\n ],\n });\n assert.ok(\n !targetContainer.style.transform.includes(\"translateX\"),\n \"the swiper has not swiped to the right because the scrollable element was scrollable to the left\"\n );\n // The scrollable element is set at its left limit\n scrollable.scrollLeft = 0;\n await triggerEvent(scrollable, \".large-text\", \"touchstart\", {\n touches: [\n {\n identifier: 0,\n clientX: scrollable.clientLeft,\n clientY:\n scrollable.getBoundingClientRect().top +\n scrollable.getBoundingClientRect().height / 2,\n target: scrollable.querySelector(\".large-text\"),\n },\n ],\n });\n await triggerEvent(scrollable, \".large-text\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: scrollable.clientWidth,\n clientY:\n scrollable.getBoundingClientRect().top +\n scrollable.getBoundingClientRect().height / 2,\n target: scrollable.querySelector(\".large-text\"),\n },\n ],\n });\n assert.ok(\n targetContainer.style.transform.includes(\"translateX\"),\n \"the swiper has swiped to the right because the scrollable element couldn't scroll anymore to the left\"\n );\n await triggerEvent(target, \".o_actionswiper\", \"touchend\", {});\n execRegisteredTimeouts();\n await nextTick();\n // In rtl languages, actions are permuted\n assert.verifySteps([\"onLeftSwipe\"], \"the onLeftSwipe props action has been performed\");\n await triggerEvent(scrollable, \".large-text\", \"touchstart\", {\n touches: [\n {\n identifier: 0,\n clientX: scrollable.clientWidth,\n clientY:\n scrollable.getBoundingClientRect().top +\n scrollable.getBoundingClientRect().height / 2,\n target: scrollable.querySelector(\".large-text\"),\n },\n ],\n });\n await triggerEvent(scrollable, \".large-text\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: scrollable.clientLeft,\n clientY:\n scrollable.getBoundingClientRect().top +\n scrollable.getBoundingClientRect().height / 2,\n target: scrollable.querySelector(\".large-text\"),\n },\n ],\n });\n assert.ok(\n !targetContainer.style.transform.includes(\"translateX\"),\n \"the swiper has not swiped to the left because the scrollable element was scrollable to the right\"\n );\n // The scrollable element is set at its right limit\n scrollable.scrollLeft =\n scrollable.scrollWidth - scrollable.getBoundingClientRect().right;\n await triggerEvent(scrollable, \".large-text\", \"touchstart\", {\n touches: [\n {\n identifier: 0,\n clientX: scrollable.clientWidth,\n clientY:\n scrollable.getBoundingClientRect().top +\n scrollable.getBoundingClientRect().height / 2,\n target: scrollable.querySelector(\".large-text\"),\n },\n ],\n });\n await triggerEvent(scrollable, \".large-text\", \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: scrollable.clientLeft,\n clientY:\n scrollable.getBoundingClientRect().top +\n scrollable.getBoundingClientRect().height / 2,\n target: scrollable.querySelector(\".large-text\"),\n },\n ],\n });\n assert.ok(\n targetContainer.style.transform.includes(\"translateX\"),\n \"the swiper has swiped to the left because the scrollable element couldn't scroll anymore to the right\"\n );\n await triggerEvent(target, \".o_actionswiper\", \"touchend\", {});\n execRegisteredTimeouts();\n await nextTick();\n // In rtl languages, actions are permuted\n assert.verifySteps(\n [\"onRightSwipe\"],\n \"the onRightSwipe props action has been performed\"\n );\n }\n );\n});\n", "/** @odoo-module **/\n\nimport { findElement, triggerEvent } from \"../helpers/utils\";\n\nasync function swipe(target, selector, direction) {\n const touchTarget = findElement(target, selector);\n if (direction === \"left\") {\n // The scrollable element is set at its right limit\n touchTarget.scrollLeft = touchTarget.scrollWidth - touchTarget.offsetWidth;\n } else {\n // The scrollable element is set at its left limit\n touchTarget.scrollLeft = 0;\n }\n\n await triggerEvent(target, selector, \"touchstart\", {\n touches: [\n {\n identifier: 0,\n clientX: 0,\n clientY: 0,\n target: touchTarget,\n },\n ],\n });\n await triggerEvent(target, selector, \"touchmove\", {\n touches: [\n {\n identifier: 0,\n clientX: (direction === \"left\" ? -1 : 1) * touchTarget.clientWidth,\n clientY: 0,\n target: touchTarget,\n },\n ],\n });\n await triggerEvent(target, selector, \"touchend\", {});\n}\n\n/**\n * Will simulate a swipe right on the target element with the given selector.\n *\n * @param {HTMLElement} target\n * @param {DOMSelector} [selector]\n * @returns {Promise}\n */\nexport async function swipeRight(target, selector) {\n return swipe(target, selector, \"right\");\n}\n\n/**\n * Will simulate a swipe left on the target element with the given selector.\n *\n * @param {HTMLElement} target\n * @param {DOMSelector} [selector]\n * @returns {Promise}\n */\nexport async function swipeLeft(target, selector) {\n return swipe(target, selector, \"left\");\n}\n\n/**\n * Simulate a \"TAP\" (touch) on the target element with the given selector.\n *\n * @param {HTMLElement} target\n * @param {DOMSelector} [selector]\n * @returns {Promise}\n */\nexport async function tap(target, selector) {\n const touchTarget = findElement(target, selector);\n const box = touchTarget.getBoundingClientRect();\n const x = box.left + box.width / 2;\n const y = box.top + box.height / 2;\n const touch = {\n identifier: 0,\n target: touchTarget,\n clientX: x,\n clientY: y,\n pageX: x,\n pageY: y,\n };\n await triggerEvent(touchTarget, null, \"touchstart\", {\n touches: [touch],\n });\n await triggerEvent(touchTarget, null, \"touchend\", {});\n}\n\n/**\n * Simulate a \"TAP\" (touch) on the target element with the given selector.\n *\n * @param {HTMLElement} target\n * @param {DOMSelector} startSelector\n * @param {DOMSelector} endSelector\n * @param {{start: \"center\"|\"top\", end: \"center\"|\"bottom\"}} [positions]\n * Specify where the touches will occur in the start and end elements.\n * @returns {Promise}\n */\nexport async function tapAndMove(\n target,\n startSelector,\n endSelector,\n positions = { start: \"center\", end: \"center\" }\n) {\n const startTarget = findElement(target, startSelector);\n const startBox = startTarget.getBoundingClientRect();\n\n const touch = {\n identifier: 0,\n target: startTarget,\n pageX: startBox.x + startBox.width / 2,\n pageY: positions.start === \"center\" ? startBox.y + startBox.height / 2 : startBox.y + 1,\n };\n await triggerEvent(startTarget, null, \"touchstart\", {\n touches: [touch],\n });\n const endTarget = findElement(target, endSelector);\n const endBox = endTarget.getBoundingClientRect();\n touch.pageX = endBox.x + endBox.width / 2;\n touch.pageY = positions.end === \"center\" ? endBox.y + endBox.height / 2 : endBox.y - 1;\n await triggerEvent(startTarget, null, \"touchmove\", {\n touches: [touch],\n });\n await triggerEvent(startTarget, null, \"touchend\", {\n touches: [touch],\n });\n}\n", "/** @odoo-module **/\n\nimport { click, getFixture, triggerEvent, nextTick } from \"@web/../tests/helpers/utils\";\nimport { ControlPanel } from \"@web/search/control_panel/control_panel\";\nimport {\n editSearch,\n makeWithSearch,\n setupControlPanelServiceRegistry,\n} from \"@web/../tests/search/helpers\";\nimport { registry } from \"@web/core/registry\";\nimport { uiService } from \"@web/core/ui/ui_service\";\n\nlet serverData;\nlet target;\n\nQUnit.module(\"Search\", (hooks) => {\n hooks.beforeEach(async () => {\n setupControlPanelServiceRegistry();\n target = getFixture();\n registry.category(\"services\").add(\"ui\", uiService);\n\n serverData = {\n models: {\n foo: {\n fields: {\n birthday: { string: \"Birthday\", type: \"date\", store: true, sortable: true },\n date_field: { string: \"Date\", type: \"date\", store: true, sortable: true },\n },\n records: [{ date_field: \"2022-02-14\" }],\n },\n },\n views: {\n \"foo,false,search\": `\n \n \n \n \n `,\n },\n };\n });\n\n QUnit.module(\"Control Panel (mobile)\");\n\n QUnit.test(\"Display control panel mobile\", async (assert) => {\n await makeWithSearch({\n serverData,\n resModel: \"foo\",\n Component: ControlPanel,\n searchMenuTypes: [\"filter\"],\n searchViewId: false,\n });\n\n assert.containsOnce(target, \".breadcrumb\");\n assert.containsOnce(target, \".o_enable_searchview\");\n assert.containsNone(target, \".o_searchview\");\n assert.containsNone(target, \".o_toggle_searchview_full\");\n\n await click(target, \".o_enable_searchview\");\n\n assert.containsNone(target, \".breadcrumb\");\n assert.containsOnce(target, \".o_enable_searchview\");\n assert.containsOnce(target, \".o_searchview\");\n assert.containsOnce(target, \".o_toggle_searchview_full\");\n\n await click(target, \".o_toggle_searchview_full\");\n\n assert.containsOnce(document.body, \".o_searchview.o_mobile_search\");\n assert.containsN(document.body, \".o_mobile_search .o_mobile_search_button\", 2);\n assert.strictEqual(\n document.body.querySelector(\".o_mobile_search_header\").textContent.trim(),\n \"FILTER CLEAR\"\n );\n assert.containsOnce(document.body, \".o_searchview.o_mobile_search .o_cp_searchview\");\n assert.containsOnce(document.body, \".o_searchview.o_mobile_search .o_mobile_search_footer\");\n\n await click(document.body.querySelector(\".o_mobile_search_button\"));\n\n assert.containsNone(target, \".breadcrumb\");\n assert.containsOnce(target, \".o_enable_searchview\");\n assert.containsOnce(target, \".o_searchview\");\n assert.containsOnce(target, \".o_toggle_searchview_full\");\n\n await click(target, \".o_enable_searchview\");\n\n assert.containsOnce(target, \".breadcrumb\");\n assert.containsOnce(target, \".o_enable_searchview\");\n assert.containsNone(target, \".o_searchview\");\n assert.containsNone(target, \".o_toggle_searchview_full\");\n });\n\n QUnit.test(\"Make a simple search in mobile mode\", async (assert) => {\n await makeWithSearch({\n serverData,\n resModel: \"foo\",\n Component: ControlPanel,\n searchMenuTypes: [\"filter\"],\n searchViewFields: {\n birthday: { string: \"Birthday\", type: \"date\", store: true, sortable: true },\n },\n searchViewArch: `\n \n \n \n `,\n });\n assert.containsNone(target, \".o_searchview\");\n\n await click(target, \".o_enable_searchview\");\n assert.containsOnce(target, \".o_searchview\");\n const input = target.querySelector(\".o_searchview input\");\n assert.containsNone(target, \".o_searchview_autocomplete\");\n\n await editSearch(target, \"2022-02-14\");\n assert.strictEqual(input.value, \"2022-02-14\", \"input value should be updated\");\n assert.containsOnce(target, \".o_searchview_autocomplete\");\n\n await triggerEvent(input, null, \"keydown\", { key: \"Escape\" });\n assert.containsNone(target, \".o_searchview_autocomplete\");\n\n await click(target, \".o_enable_searchview\");\n assert.containsNone(target, \".o_searchview\");\n });\n\n QUnit.test(\"Control panel is shown/hide on top when scrolling\", async (assert) => {\n await makeWithSearch({\n serverData,\n resModel: \"foo\",\n Component: ControlPanel,\n searchMenuTypes: [\"filter\"],\n });\n const contentHeight = 200;\n const sampleContent = document.createElement(\"div\");\n sampleContent.style.minHeight = `${2 * contentHeight}px`;\n target.appendChild(sampleContent);\n const { maxHeight, overflow } = target.style;\n target.style.maxHeight = `${contentHeight}px`;\n target.style.overflow = \"auto\";\n target.scrollTo({ top: 50 });\n await nextTick();\n\n assert.hasClass(\n target.querySelector(\".o_control_panel\"),\n \"o_mobile_sticky\",\n \"control panel becomes sticky when the target is not on top\"\n );\n target.scrollTo({ top: -50 });\n await nextTick();\n\n assert.doesNotHaveClass(\n target.querySelector(\".o_control_panel\"),\n \"o_mobile_sticky\",\n \"control panel is not sticky anymore\"\n );\n target.style.maxHeight = maxHeight;\n target.style.overflow = overflow;\n });\n});\n", "/** @odoo-module **/\n\nimport { click, getFixture } from \"@web/../tests/helpers/utils\";\nimport { SearchPanel } from \"@web/search/search_panel/search_panel\";\nimport { makeWithSearch, setupControlPanelServiceRegistry } from \"@web/../tests/search/helpers\";\nimport { registry } from \"@web/core/registry\";\nimport { uiService } from \"@web/core/ui/ui_service\";\n\nimport { Component, xml } from \"@odoo/owl\";\n\nlet serverData;\nlet target;\n\nQUnit.module(\"Search\", (hooks) => {\n hooks.beforeEach(async () => {\n setupControlPanelServiceRegistry();\n target = getFixture();\n registry.category(\"services\").add(\"ui\", uiService);\n\n serverData = {\n models: {\n foo: {\n fields: {\n tag_id: {\n string: \"Many2One\",\n type: \"many2one\",\n relation: \"tag\",\n store: true,\n sortable: true,\n },\n },\n records: [\n { id: 1, tag_id: 2 },\n { id: 2, tag_id: 1 },\n { id: 3, tag_id: 1 },\n ],\n },\n tag: {\n fields: {\n name: { string: \"Name\", type: \"string\" },\n },\n records: [\n { id: 1, name: \"Gold\" },\n { id: 2, name: \"Silver\" },\n ],\n },\n },\n };\n });\n\n QUnit.module(\"Search Panel (mobile)\");\n\n QUnit.test(\"basic search panel rendering\", async (assert) => {\n class Parent extends Component {}\n Parent.components = { SearchPanel };\n Parent.template = xml``;\n await makeWithSearch({\n serverData,\n resModel: \"foo\",\n Component: Parent,\n searchViewFields: serverData.models.foo.fields,\n searchViewArch: `\n \n \n \n \n `,\n });\n assert.containsOnce(target, \".o_search_panel.o_search_panel_summary\");\n\n await click(target, \".o_search_panel .o_search_panel_current_selection\");\n assert.containsOnce(document.body, \".o_search_panel.o_mobile_search\");\n assert.containsN(document.body, \".o_search_panel_category_value\", 3); // All, Gold, Silver\n });\n});\n", "/** @odoo-module **/\n\nimport { browser } from \"@web/core/browser/browser\";\nimport { CalendarCommonRenderer } from \"@web/views/calendar/calendar_common/calendar_common_renderer\";\nimport { CalendarYearRenderer } from \"@web/views/calendar/calendar_year/calendar_year_renderer\";\nimport { click, getFixture, nextTick, patchDate, patchWithCleanup } from \"../../helpers/utils\";\nimport { changeScale, clickEvent, toggleSectionFilter } from \"../../views/calendar/helpers\";\nimport { makeView, setupViewRegistries } from \"../../views/helpers\";\nimport { tap, swipeRight, tapAndMove } from \"../helpers\";\n\nlet target;\nlet serverData;\n\nQUnit.module(\"Views\", ({ beforeEach }) => {\n beforeEach(() => {\n // 2016-12-12 08:00:00\n patchDate(2016, 11, 12, 8, 0, 0);\n patchWithCleanup(browser, {\n setTimeout: (fn) => fn(),\n clearTimeout: () => {},\n });\n\n target = getFixture();\n\n setupViewRegistries();\n\n serverData = {\n models: {\n event: {\n fields: {\n id: { string: \"ID\", type: \"integer\" },\n name: { string: \"name\", type: \"char\" },\n start: { string: \"start datetime\", type: \"datetime\" },\n stop: { string: \"stop datetime\", type: \"datetime\" },\n partner_id: {\n string: \"user\",\n type: \"many2one\",\n relation: \"partner\",\n related: \"user_id.partner_id\",\n default: 1,\n },\n },\n records: [\n {\n id: 1,\n partner_id: 1,\n name: \"event 1\",\n start: \"2016-12-11 00:00:00\",\n stop: \"2016-12-11 00:00:00\",\n },\n {\n id: 2,\n partner_id: 2,\n name: \"event 2\",\n start: \"2016-12-12 10:55:05\",\n stop: \"2016-12-12 14:55:05\",\n },\n ],\n methods: {\n check_access_rights() {\n return Promise.resolve(true);\n },\n },\n },\n partner: {\n fields: {\n id: { string: \"ID\", type: \"integer\" },\n image: { string: \"Image\", type: \"binary\" },\n display_name: { string: \"Displayed name\", type: \"char\" },\n },\n records: [\n { id: 1, display_name: \"partner 1\", image: \"AAA\" },\n { id: 2, display_name: \"partner 2\", image: \"BBB\" },\n ],\n },\n },\n };\n });\n\n QUnit.module(\"CalendarView - Mobile\");\n\n QUnit.test(\"simple calendar rendering in mobile\", async function (assert) {\n await makeView({\n type: \"calendar\",\n resModel: \"event\",\n arch: `\n \n \n `,\n serverData,\n });\n\n assert.containsNone(target, \".o_calendar_button_prev\", \"prev button should be hidden\");\n assert.containsNone(target, \".o_calendar_button_next\", \"next button should be hidden\");\n assert.isVisible(\n target.querySelector(\n \".o_cp_bottom_left .o_calendar_buttons .o_view_scale_selector + button.o_cp_today_button\"\n ),\n \"today button should be visible near the calendar buttons (bottom left corner)\"\n );\n\n // Test all views\n // displays month mode by default\n assert.containsOnce(\n target,\n \".fc-view-container > .fc-timeGridWeek-view\",\n \"should display the current week\"\n );\n assert.equal(\n target.querySelector(\".breadcrumb-item\").textContent,\n \"undefined (Dec 11 \u2013 17, 2016)\"\n );\n\n // switch to day mode\n await click(target, \".o_control_panel .scale_button_selection\");\n await click(target, \".o_control_panel .o_scale_button_day\");\n await nextTick();\n assert.containsOnce(\n target,\n \".fc-view-container > .fc-timeGridDay-view\",\n \"should display the current day\"\n );\n assert.equal(\n target.querySelector(\".breadcrumb-item\").textContent,\n \"undefined (December 12, 2016)\"\n );\n\n // switch to month mode\n await click(target, \".o_control_panel .scale_button_selection\");\n await click(target, \".o_control_panel .o_scale_button_month\");\n await nextTick();\n assert.containsOnce(\n target,\n \".fc-view-container > .fc-dayGridMonth-view\",\n \"should display the current month\"\n );\n assert.equal(\n target.querySelector(\".breadcrumb-item\").textContent,\n \"undefined (December 2016)\"\n );\n\n // switch to year mode\n await click(target, \".o_control_panel .scale_button_selection\");\n await click(target, \".o_control_panel .o_scale_button_year\");\n await nextTick();\n assert.containsOnce(\n target,\n \".fc-view-container > .fc-dayGridYear-view\",\n \"should display the current year\"\n );\n assert.equal(target.querySelector(\".breadcrumb-item\").textContent, \"undefined (2016)\");\n });\n\n QUnit.test(\"calendar: popover is rendered as dialog in mobile\", async function (assert) {\n // Legacy name of this test: \"calendar: popover rendering in mobile\"\n await makeView({\n type: \"calendar\",\n resModel: \"event\",\n serverData,\n arch: `\n \n \n `,\n });\n\n await clickEvent(target, 1);\n assert.containsNone(target, \".o_cw_popover\");\n assert.containsOnce(target, \".modal\");\n assert.hasClass(target.querySelector(\".modal\"), \"o_modal_full\");\n\n assert.containsN(target, \".modal-footer .btn\", 2);\n assert.containsOnce(target, \".modal-footer .btn.btn-primary.o_cw_popover_edit\");\n assert.containsOnce(target, \".modal-footer .btn.btn-secondary.o_cw_popover_delete\");\n });\n\n QUnit.test(\"calendar: today button\", async function (assert) {\n await makeView({\n type: \"calendar\",\n resModel: \"event\",\n serverData,\n arch: ``,\n });\n assert.equal(target.querySelector(\".fc-day-header[data-date]\").dataset.date, \"2016-12-12\");\n\n // Swipe right\n await swipeRight(target, \".o_calendar_widget\");\n assert.equal(target.querySelector(\".fc-day-header[data-date]\").dataset.date, \"2016-12-11\");\n\n await click(target, \".o_calendar_button_today\");\n assert.equal(target.querySelector(\".fc-day-header[data-date]\").dataset.date, \"2016-12-12\");\n });\n\n QUnit.test(\"calendar: show and change other calendar\", async function (assert) {\n await makeView({\n type: \"calendar\",\n resModel: \"event\",\n serverData,\n arch: `\n \n \n \n `,\n });\n\n assert.containsOnce(target, \".o_other_calendar_panel\");\n assert.containsN(\n target,\n \".o_other_calendar_panel .o_filter > *\",\n 3,\n \"should contains 3 child nodes -> 1 label (USER) + 2 resources (user 1/2)\"\n );\n assert.containsNone(target, \".o_calendar_sidebar\");\n assert.containsOnce(target, \".o_calendar_renderer\");\n\n // Toggle the other calendar panel should hide the calendar view and show the sidebar\n await click(target, \".o_other_calendar_panel\");\n assert.containsOnce(target, \".o_calendar_sidebar\");\n assert.containsNone(target, \".o_calendar_renderer\");\n assert.containsOnce(target, \".o_calendar_filter\");\n assert.containsOnce(target, \".o_calendar_filter[data-name=partner_id]\");\n\n // Toggle the whole section filters by unchecking the all items checkbox\n await toggleSectionFilter(target, \"partner_id\");\n assert.containsN(\n target,\n \".o_other_calendar_panel .o_filter > *\",\n 1,\n \"should contains 1 child node -> 1 label (USER)\"\n );\n\n // Toggle again the other calendar panel should hide the sidebar and show the calendar view\n await click(target, \".o_other_calendar_panel\");\n assert.containsNone(target, \".o_calendar_sidebar\");\n assert.containsOnce(target, \".o_calendar_renderer\");\n });\n\n QUnit.test('calendar: tap on \"Free Zone\" opens quick create', async function (assert) {\n patchWithCleanup(CalendarCommonRenderer.prototype, {\n onDateClick(...args) {\n assert.step(\"dateClick\");\n return this._super(...args);\n },\n onSelect(...args) {\n assert.step(\"select\");\n return this._super(...args);\n },\n });\n\n await makeView({\n type: \"calendar\",\n resModel: \"event\",\n serverData,\n arch: ``,\n });\n\n // Simulate a \"TAP\" (touch)\n await tap(\n target,\n \".fc-time-grid .fc-minor[data-time='00:30:00'] .fc-widget-content:last-child\"\n );\n await nextTick();\n\n // should open a Quick create modal view in mobile on short tap\n assert.containsOnce(target, \".modal\");\n assert.verifySteps([\"dateClick\"]);\n });\n\n QUnit.test('calendar: select range on \"Free Zone\" opens quick create', async function (assert) {\n patchWithCleanup(CalendarCommonRenderer.prototype, {\n get options() {\n return Object.assign({}, this._super(), {\n selectLongPressDelay: 0,\n });\n },\n onDateClick(info) {\n assert.step(\"dateClick\");\n return this._super(info);\n },\n onSelect(info) {\n assert.step(\"select\");\n const { startStr, endStr } = info;\n assert.equal(startStr, \"2016-12-12T01:00:00+01:00\");\n assert.equal(endStr, \"2016-12-12T02:00:00+01:00\");\n return this._super(info);\n },\n });\n\n await makeView({\n type: \"calendar\",\n resModel: \"event\",\n serverData,\n arch: ``,\n });\n\n // Simulate a \"TAP\" (touch)\n await tapAndMove(\n target,\n \".fc-time-grid [data-time='01:00:00'] .fc-widget-content:last-child\",\n \".fc-time-grid [data-time='02:00:00'] .fc-widget-content:last-child\",\n { start: \"top\", end: \"bottom\" }\n );\n await nextTick();\n\n // should open a Quick create modal view in mobile on short tap\n assert.containsOnce(target, \".modal\");\n assert.verifySteps([\"select\"]);\n });\n\n QUnit.test(\"calendar (year): select date range opens quick create\", async function (assert) {\n patchWithCleanup(CalendarYearRenderer.prototype, {\n get options() {\n return Object.assign({}, this._super(), {\n longPressDelay: 0,\n selectLongPressDelay: 0,\n });\n },\n onDateClick(info) {\n assert.step(\"dateClick\");\n return this._super(info);\n },\n onSelect(info) {\n assert.step(\"select\");\n const { startStr, endStr } = info;\n assert.equal(startStr, \"2016-02-02\");\n assert.equal(endStr, \"2016-02-06\"); // end date is exclusive\n return this._super(info);\n },\n });\n\n await makeView({\n type: \"calendar\",\n resModel: \"event\",\n serverData,\n arch: ``,\n });\n\n // Tap on a date\n await tapAndMove(\n target,\n \".fc-day-top[data-date='2016-02-02']\",\n \".fc-day-top[data-date='2016-02-05']\"\n );\n await nextTick();\n\n // should open a Quick create modal view in mobile on short tap\n assert.containsOnce(target, \".modal\");\n assert.verifySteps([\"select\"]);\n });\n\n QUnit.test(\"calendar (month/year): tap on date switch to day scale\", async function (assert) {\n await makeView({\n type: \"calendar\",\n resModel: \"event\",\n serverData,\n arch: ``,\n });\n\n // Should display year view\n assert.containsOnce(target, \".fc-dayGridYear-view\");\n assert.containsN(target, \".fc-month-container\", 12);\n assert.equal(target.querySelector(\".breadcrumb-item\").textContent, \"undefined (2016)\");\n\n // Tap on a date\n await tap(target, \".fc-day-top[data-date='2016-02-05']\");\n await nextTick(); // switch renderer\n await nextTick(); // await breadcrumb update\n\n // Should display day view\n assert.containsNone(target, \".fc-dayGridYear-view\");\n assert.containsOnce(target, \".fc-timeGridDay-view\");\n assert.equal(\n target.querySelector(\".breadcrumb-item\").textContent,\n \"undefined (February 5, 2016)\"\n );\n\n // Change scale to month\n await changeScale(target, \"month\");\n assert.containsNone(target, \".fc-timeGridDay-view\");\n assert.containsOnce(target, \".fc-dayGridMonth-view\");\n assert.equal(\n target.querySelector(\".breadcrumb-item\").textContent,\n \"undefined (February 2016)\"\n );\n\n // Tap on a date\n await tap(target, \".fc-day-top[data-date='2016-02-10']\");\n await nextTick(); // await breadcrumb update\n\n assert.containsNone(target, \".fc-dayGridMonth-view\");\n assert.containsOnce(target, \".fc-timeGridDay-view\");\n assert.equal(\n target.querySelector(\".breadcrumb-item\").textContent,\n \"undefined (February 10, 2016)\"\n );\n });\n});\n", "/** @odoo-module **/\n\nimport { getFixture } from \"@web/../tests/helpers/utils\";\nimport { makeView, setupViewRegistries } from \"@web/../tests/views/helpers\";\n\nlet serverData;\nlet target;\n\nQUnit.module(\"Fields\", (hooks) => {\n hooks.beforeEach(() => {\n target = getFixture();\n serverData = {\n models: {\n partner: {\n fields: {\n display_name: { string: \"Displayed name\", type: \"char\" },\n timmy: { string: \"pokemon\", type: \"many2many\", relation: \"partner_type\" },\n },\n },\n partner_type: {\n fields: {\n name: { string: \"Partner Type\", type: \"char\" },\n },\n records: [\n { id: 12, display_name: \"gold\" },\n { id: 14, display_name: \"silver\" },\n ],\n },\n },\n };\n\n setupViewRegistries();\n });\n\n QUnit.module(\"Many2ManyTagsField\");\n\n QUnit.test(\"Many2ManyTagsField placeholder should be correct\", async function (assert) {\n await makeView({\n type: \"form\",\n resModel: \"partner\",\n serverData,\n arch: `\n
\n \n `,\n });\n assert.strictEqual(target.querySelector(\"#timmy\").placeholder, \"foo\");\n });\n\n QUnit.test(\"Many2ManyTagsField placeholder should be empty\", async function (assert) {\n await makeView({\n type: \"form\",\n resModel: \"partner\",\n serverData,\n arch: `\n
\n \n `,\n });\n assert.strictEqual(target.querySelector(\"#timmy\").placeholder, \"\");\n });\n});\n", "/** @odoo-module **/\n\nimport { AutoComplete } from \"@web/core/autocomplete/autocomplete\";\nimport { browser } from \"@web/core/browser/browser\";\nimport { click, clickSave, getFixture, patchWithCleanup } from \"@web/../tests/helpers/utils\";\nimport { makeView, setupViewRegistries } from \"@web/../tests/views/helpers\";\n\nimport * as BarcodeScanner from \"@web/webclient/barcode/barcode_scanner\";\n\nlet serverData;\nlet target;\n\nconst CREATE = \"create\";\nconst NAME_SEARCH = \"name_search\";\nconst PRODUCT_PRODUCT = \"product.product\";\nconst SALE_ORDER_LINE = \"sale_order_line\";\nconst PRODUCT_FIELD_NAME = \"product_id\";\n\n// MockRPC to allow the search in barcode too\nasync function barcodeMockRPC(route, args, performRPC) {\n if (args.method === NAME_SEARCH && args.model === PRODUCT_PRODUCT) {\n const result = await performRPC(route, args);\n const records = serverData.models[PRODUCT_PRODUCT].records\n .filter((record) => record.barcode === args.kwargs.name)\n .map((record) => [record.id, record.name]);\n return records.concat(result);\n }\n}\n\nQUnit.module(\"Fields\", (hooks) => {\n hooks.beforeEach(() => {\n target = getFixture();\n serverData = {\n models: {\n [PRODUCT_PRODUCT]: {\n fields: {\n id: { type: \"integer\" },\n name: {},\n barcode: {},\n },\n records: [\n {\n id: 111,\n name: \"product_cable_management_box\",\n barcode: \"601647855631\",\n },\n {\n id: 112,\n name: \"product_n95_mask\",\n barcode: \"601647855632\",\n },\n {\n id: 113,\n name: \"product_surgical_mask\",\n barcode: \"601647855633\",\n },\n ],\n },\n [SALE_ORDER_LINE]: {\n fields: {\n id: { type: \"integer\" },\n [PRODUCT_FIELD_NAME]: {\n string: PRODUCT_FIELD_NAME,\n type: \"many2one\",\n relation: PRODUCT_PRODUCT,\n },\n },\n },\n },\n views: {\n \"product.product,false,kanban\": `\n \n
\n \n \n \n
\n
\n `,\n \"product.product,false,search\": \"\",\n },\n };\n\n setupViewRegistries();\n\n patchWithCleanup(AutoComplete, {\n delay: 0,\n });\n\n // simulate a environment with a camera/webcam\n patchWithCleanup(\n browser,\n Object.assign({}, browser, {\n setTimeout: (fn) => fn(),\n navigator: {\n userAgent: \"Chrome/0.0.0 (Linux; Android 13; Odoo TestSuite)\",\n mediaDevices: {\n getUserMedia: () => [],\n },\n },\n })\n );\n });\n\n QUnit.module(\"Many2OneField Barcode (Small)\");\n\n QUnit.test(\"barcode button with multiple results\", async function (assert) {\n assert.expect(4);\n\n // The product selected (mock) for the barcode scanner\n const selectedRecordTest = serverData.models[PRODUCT_PRODUCT].records[1];\n\n patchWithCleanup(BarcodeScanner, {\n scanBarcode: async () => \"mask\",\n });\n\n await makeView({\n type: \"form\",\n resModel: SALE_ORDER_LINE,\n serverData,\n arch: `\n
\n \n `,\n async mockRPC(route, args, performRPC) {\n if (args.method === CREATE && args.model === SALE_ORDER_LINE) {\n const selectedId = args.args[0][PRODUCT_FIELD_NAME];\n assert.equal(\n selectedId,\n selectedRecordTest.id,\n `product id selected ${selectedId}, should be ${selectedRecordTest.id} (${selectedRecordTest.barcode})`\n );\n return performRPC(route, args, performRPC);\n }\n return barcodeMockRPC(route, args, performRPC);\n },\n });\n\n const scanButton = target.querySelector(\".o_barcode\");\n assert.containsOnce(target, scanButton, \"has scanner barcode button\");\n\n await click(target, \".o_barcode\");\n\n const modal = target.querySelector(\".modal-dialog.modal-lg\");\n assert.containsOnce(target, modal, \"there should be one modal opened in full screen\");\n\n assert.containsN(\n modal,\n \".o_kanban_record .oe_kanban_global_click\",\n 2,\n \"there should be 2 records displayed\"\n );\n\n await click(modal, \".o_kanban_record:nth-child(1)\");\n await clickSave(target);\n });\n\n QUnit.test(\"many2one with barcode show all records\", async function (assert) {\n // The product selected (mock) for the barcode scanner\n const selectedRecordTest = serverData.models[PRODUCT_PRODUCT].records[0];\n\n patchWithCleanup(BarcodeScanner, {\n scanBarcode: async () => selectedRecordTest.barcode,\n });\n\n await makeView({\n type: \"form\",\n resModel: SALE_ORDER_LINE,\n serverData,\n arch: `\n
\n \n `,\n mockRPC: barcodeMockRPC,\n });\n\n // Select one product\n await click(target, \".o_barcode\");\n\n // Click on the input to show all records\n await click(target, \".o_input_dropdown > input\");\n\n const modal = target.querySelector(\".modal-dialog.modal-lg\");\n assert.containsOnce(target, modal, \"there should be one modal opened in full screen\");\n\n assert.containsN(\n modal,\n \".o_kanban_record .oe_kanban_global_click\",\n 3,\n \"there should be 3 records displayed\"\n );\n });\n});\n", "/** @odoo-module **/\n\nimport { click, getFixture } from \"@web/../tests/helpers/utils\";\nimport { makeView, setupViewRegistries } from \"@web/../tests/views/helpers\";\n\nlet fixture;\nlet serverData;\n\nQUnit.module(\"Mobile Fields\", ({ beforeEach }) => {\n beforeEach(() => {\n setupViewRegistries();\n fixture = getFixture();\n serverData = {\n models: {\n partner: {\n fields: {\n display_name: { string: \"Displayed name\", type: \"char\" },\n trululu: { string: \"Trululu\", type: \"many2one\", relation: \"partner\" },\n },\n records: [\n { id: 1, display_name: \"first record\", trululu: 4 },\n { id: 2, display_name: \"second record\", trululu: 1 },\n { id: 4, display_name: \"aaa\" },\n ],\n },\n },\n };\n });\n\n QUnit.module(\"StatusBarField\");\n\n QUnit.test(\"statusbar is rendered correclty on small devices\", async (assert) => {\n await makeView({\n type: \"form\",\n resModel: \"partner\",\n resId: 1,\n serverData,\n arch: `\n
\n
\n \n
\n \n \n `,\n });\n\n assert.containsOnce(\n fixture,\n \".o_statusbar_status > button\",\n \"should have only one visible status in mobile, the active one\"\n );\n assert.containsOnce(\n fixture,\n \".o_statusbar_status .dropdown\",\n \"should have a dropdown containing all status\"\n );\n assert.containsNone(\n fixture,\n \".o_statusbar_status .dropdown-menu\",\n \"dropdown should be hidden\"\n );\n assert.strictEqual(\n fixture.querySelector(\".o_statusbar_status button.dropdown-toggle\").textContent.trim(),\n \"aaa\",\n \"statusbar button should display current field value\"\n );\n\n // open the dropdown\n await click(fixture, \".o_statusbar_status > button\");\n assert.containsOnce(\n fixture,\n \".o_statusbar_status .dropdown-menu\",\n \"dropdown should be visible\"\n );\n assert.containsN(\n fixture,\n \".o_statusbar_status .dropdown-menu .btn\",\n 3,\n \"should have 3 status\"\n );\n assert.containsN(\n fixture,\n \".o_statusbar_status .btn.disabled\",\n 3,\n \"all status should be disabled\"\n );\n assert.hasClass(\n fixture.querySelector(\".o_statusbar_status .btn:nth-child(3)\"),\n \"btn-primary\",\n \"active status should be btn-primary\"\n );\n });\n\n QUnit.test(\"statusbar with no status on extra small screens\", async (assert) => {\n await makeView({\n type: \"form\",\n resModel: \"partner\",\n resId: 4,\n serverData,\n arch: `\n
\n
\n \n
\n
\n `,\n });\n\n assert.doesNotHaveClass(\n fixture.querySelector(\".o_field_statusbar\"),\n \"o_field_empty\",\n \"statusbar widget should have class o_field_empty in edit\"\n );\n assert.containsOnce(\n fixture,\n \".o_statusbar_status button.dropdown-toggle\",\n \"statusbar widget should have a button\"\n );\n assert.strictEqual(\n fixture.querySelector(\".o_statusbar_status button.dropdown-toggle\").textContent.trim(),\n \"\",\n \"statusbar button shouldn't have text for null field value\"\n );\n\n await click(fixture, \".o_statusbar_status button.dropdown-toggle\");\n assert.containsOnce(\n fixture,\n \".o_statusbar_status .dropdown-menu\",\n \"statusbar widget should have a dropdown menu\"\n );\n assert.containsN(\n fixture,\n \".o_statusbar_status .dropdown-menu .btn\",\n 3,\n \"statusbar widget dropdown menu should have 3 buttons\"\n );\n assert.strictEqual(\n fixture\n .querySelectorAll(\".o_statusbar_status .dropdown-menu .btn\")[0]\n .textContent.trim(),\n \"first record\",\n \"statusbar widget dropdown first button should display the first record display_name\"\n );\n assert.strictEqual(\n fixture\n .querySelectorAll(\".o_statusbar_status .dropdown-menu .btn\")[1]\n .textContent.trim(),\n \"second record\",\n \"statusbar widget dropdown second button should display the second record display_name\"\n );\n assert.strictEqual(\n fixture\n .querySelectorAll(\".o_statusbar_status .dropdown-menu .btn\")[2]\n .textContent.trim(),\n \"aaa\",\n \"statusbar widget dropdown third button should display the third record display_name\"\n );\n });\n\n QUnit.test(\"clickable statusbar widget on mobile view\", async (assert) => {\n await makeView({\n type: \"form\",\n resModel: \"partner\",\n resId: 1,\n serverData,\n arch: `\n
\n
\n \n
\n
\n `,\n });\n\n await click(fixture, \".o_statusbar_status .dropdown-toggle\");\n assert.hasClass(\n fixture.querySelector(\".o_statusbar_status .dropdown-menu .btn:nth-child(3)\"),\n \"btn-primary\"\n );\n assert.hasClass(\n fixture.querySelector(\".o_statusbar_status .dropdown-menu .btn:nth-child(3)\"),\n \"disabled\"\n );\n\n assert.containsN(\n fixture,\n \".o_statusbar_status .btn-secondary:not(.dropdown-toggle):not(.disabled)\",\n 2,\n \"other status should be btn-secondary and not disabled\"\n );\n\n await click(\n fixture.querySelector(\n \".o_statusbar_status .btn-secondary:not(.dropdown-toggle):not(.disabled)\"\n )\n );\n\n await click(fixture, \".o_statusbar_status .dropdown-toggle\");\n assert.hasClass(\n fixture.querySelector(\".o_statusbar_status .dropdown-menu .btn:nth-child(1)\"),\n \"btn-primary\"\n );\n assert.hasClass(\n fixture.querySelector(\".o_statusbar_status .dropdown-menu .btn:nth-child(1)\"),\n \"disabled\"\n );\n });\n});\n", "/** @odoo-module **/\n\nimport { registry } from \"@web/core/registry\";\nimport {\n click,\n clickSave,\n editInput,\n getFixture,\n makeDeferred,\n nextTick,\n patchWithCleanup,\n} from \"@web/../tests/helpers/utils\";\nimport { makeView, setupViewRegistries } from \"@web/../tests/views/helpers\";\nimport { AttachDocumentWidget } from \"@web/views/widgets/attach_document/attach_document\";\n\nlet fixture;\nlet serverData;\n\nconst serviceRegistry = registry.category(\"services\");\n\nQUnit.module(\"Mobile Views\", ({ beforeEach }) => {\n beforeEach(() => {\n setupViewRegistries();\n fixture = getFixture();\n serverData = {\n models: {\n partner: {\n fields: {\n display_name: { type: \"char\", string: \"Display Name\" },\n trululu: { type: \"many2one\", string: \"Trululu\", relation: \"partner\" },\n },\n records: [\n { id: 1, display_name: \"first record\", trululu: 4 },\n { id: 2, display_name: \"second record\", trululu: 1 },\n { id: 4, display_name: \"aaa\" },\n ],\n },\n },\n };\n });\n\n QUnit.module(\"FormView\");\n\n QUnit.test(`statusbar buttons are correctly rendered in mobile`, async (assert) => {\n await makeView({\n type: \"form\",\n resModel: \"partner\",\n resId: 1,\n serverData,\n arch: `\n
\n
\n
\n \n \n \n \n
`;\n\n const target = getFixture();\n const env = makeTestEnv();\n\n await mount(Parent, target, { env });\n\n assert.containsNone(document.body, \".o_popover\");\n await testUtils.dom.click(document.querySelector(\"#target\"));\n assert.containsOnce(document.body, \".o_popover\");\n assert.verifySteps([\"overrideBackButton: true\"]);\n // simulate 'backbutton' event triggered by the app\n await testUtils.dom.triggerEvent(document, \"backbutton\");\n assert.verifySteps([\"overrideBackButton: false\"]);\n assert.containsNone(document.body, \".o_popover\", \"should have been closed\");\n\n });\n\n QUnit.module(\"ControlPanel\");\n\n QUnit.test(\"mobile search: close with backbutton event\", async function (assert) {\n assert.expect(7);\n\n patchWithCleanup(mobile.methods, {\n overrideBackButton({ enabled }) {\n assert.step(`overrideBackButton: ${enabled}`);\n },\n });\n\n const actions = {\n 1: {\n id: 1,\n name: \"Yes\",\n res_model: \"partner\",\n type: \"ir.actions.act_window\",\n views: [[false, \"list\"]],\n },\n };\n\n const views = {\n \"partner,false,list\": '',\n \"partner,false,search\": `\n \n \n \n \n \n `,\n });\n await clickSave(target);\n });\n\n QUnit.test(\"controller should call native updateAccount method with SVG avatar when saving record\", async function (assert) {\n assert.expect(4);\n\n patchWithCleanup(mobile.methods, {\n updateAccount( options ) {\n const { avatar, name, username } = options;\n assert.ok(\"should call updateAccount\");\n assert.ok(avatar.startsWith(BASE64_PNG_HEADER), \"should have a PNG base64 encoded avatar\");\n assert.strictEqual(name, \"Marc Demo\");\n assert.strictEqual(username, \"demo\");\n return Promise.resolve();\n }\n });\n\n patchWithCleanup(session, {\n url(path) {\n if (path === '/web/image') {\n return `data:image/svg+xml;base64,${BASE64_SVG_IMAGE}`;\n }\n return this._super(...arguments);\n },\n username: \"demo\",\n name: \"Marc Demo\",\n });\n\n await makeView({\n type: \"form\",\n resModel: \"partner\",\n serverData: serverData,\n arch: `\n
\n \n \n \n
`,\n });\n\n await clickSave(target);\n });\n\n QUnit.test(\"UserPreferencesFormView should call native updateAccount method when saving record\", async function (assert) {\n assert.expect(4);\n\n patchWithCleanup(mobile.methods, {\n updateAccount( options ) {\n const { avatar, name, username } = options;\n assert.ok(\"should call updateAccount\");\n assert.ok(avatar.startsWith(BASE64_PNG_HEADER), \"should have a PNG base64 encoded avatar\");\n assert.strictEqual(name, \"Marc Demo\");\n assert.strictEqual(username, \"demo\");\n return Promise.resolve();\n }\n });\n\n patchWithCleanup(session, {\n url(path) {\n if (path === '/web/image') {\n return `data:image/png;base64,${MY_IMAGE}`;\n }\n return this._super(...arguments);\n },\n username: \"demo\",\n name: \"Marc Demo\",\n });\n\n await makeView({\n type: \"form\",\n resModel: \"users\",\n serverData: serverData,\n arch: `\n
\n \n \n \n
`,\n });\n\n await clickSave(target);\n });\n});\n", "odoo.define('web_grid.grid_mobile_tests', function (require) {\n\"use strict\";\n\nlet GridView = require('web_grid.GridView');\nlet testUtils = require('web.test_utils');\n\nlet createView = testUtils.createView;\n\nQUnit.module('LegacyViews', {\n beforeEach: function () {\n this.data = {\n 'analytic.line': {\n fields: {\n project_id: {string: \"Project\", type: \"many2one\", relation: \"project\"},\n task_id: {string: \"Task\", type: \"many2one\", relation: \"task\"},\n date: {string: \"Date\", type: \"date\"},\n unit_amount: {string: \"Unit Amount\", type: \"float\"},\n },\n records: [\n {id: 1, project_id: 31, date: \"2017-01-24\", unit_amount: 2.5},\n {id: 2, project_id: 31, task_id: 1, date: \"2017-01-25\", unit_amount: 2},\n {id: 3, project_id: 31, task_id: 1, date: \"2017-01-25\", unit_amount: 5.5},\n {id: 4, project_id: 31, task_id: 1, date: \"2017-01-30\", unit_amount: 10},\n {id: 5, project_id: 142, task_id: 12, date: \"2017-01-31\", unit_amount: 3.5},\n ]\n },\n project: {\n fields: {\n name: {string: \"Project Name\", type: \"char\"}\n },\n records: [\n {id: 31, display_name: \"P1\"},\n {id: 142, display_name: \"Webocalypse Now\"},\n ]\n },\n task: {\n fields: {\n name: {string: \"Task Name\", type: \"char\"},\n project_id: {string: \"Project\", type: \"many2one\", relation: \"project\"},\n },\n records: [\n {id: 1, display_name: \"BS task\", project_id: 31},\n {id: 12, display_name: \"Another BS task\", project_id: 142},\n {id: 54, display_name: \"yet another task\", project_id: 142},\n ]\n },\n };\n this.arch = `\n \n \n \n \n \n \n \n \n `;\n }\n}, function () {\n QUnit.module('GridView (legacy) - Mobile');\n\n QUnit.test('basic grid view, range button in mobile', async function (assert) {\n assert.expect(5);\n let countCallRPC = 0;\n let grid = await createView({\n View: GridView,\n model: 'analytic.line',\n data: this.data,\n arch: this.arch,\n currentDate: \"2017-01-25\",\n mockRPC: function (route, args) {\n if (args.method === 'read_grid') {\n if (countCallRPC === 0) {\n assert.equal(args.kwargs.range.span, 'day', \"range should be day\");\n } else if (countCallRPC === 1) {\n assert.equal(args.kwargs.range.span, 'week', \"range should be month\");\n }\n }\n countCallRPC++;\n return this._super.apply(this, arguments);\n },\n });\n\n await testUtils.nextTick();\n assert.equal(grid.$('table').length, 1, \"should have rendered one table\");\n\n let btnCal = grid.$buttons.find('.btn-group.o_grid_range > button.btn');\n assert.equal(btnCal.length, 1, \"should have a calendar button for range\");\n await testUtils.dom.click(btnCal);\n\n // Day range should be automatically added.\n let btnRange = grid.$buttons.find('.btn-group.o_grid_range button.grid_arrow_range');\n assert.equal(btnRange.length, 2, \"should have two range buttons (Day and Week)\");\n\n await testUtils.dom.click(grid.$buttons.find('button[data-name=week]'));\n\n grid.destroy();\n });\n\n QUnit.test('grid view should open in day range for mobile', async function (assert) {\n assert.expect(1);\n\n const grid = await createView({\n View: GridView,\n model: 'analytic.line',\n data: this.data,\n arch: `\n \n \n \n \n \n \n \n `,\n currentDate: \"2017-01-25\",\n });\n\n const btnRangeDay = grid.$buttons[0].querySelector('button[data-name=day]');\n assert.hasClass(btnRangeDay, \"active\", \"Grid view should be open in day range for mobile\");\n\n grid.destroy();\n });\n});\n});\n", "odoo.define('web_grid.MockServer', function (require) {\n\"use strict\";\n\nvar MockServer = require('web.MockServer');\n\nMockServer.include({\n /**\n * @override\n */\n _performRpc: function (route, args) {\n if (args.method === 'read_grid') {\n return this._mockReadGrid(args.model, args.kwargs);\n } else if (args.method === 'read_grid_grouped') {\n return this._mockReadGridGrouped(args.model, args.kwargs);\n } else if (args.method === 'adjust_grid') {\n var domain = args.args[1];\n var columnField = args.args[2];\n var columnValue = args.args[3];\n var cellField = args.args[4];\n var change = args.args[5];\n var lines = this._mockSearchReadController({\n model: args.model,\n domain: domain,\n fields: [],\n });\n var newID = this._mockCopy(args.model, lines.records[0].id);\n var newRecord = _.findWhere(this.data[args.model].records, {id: newID});\n newRecord[cellField] = change;\n newRecord[columnField] = columnValue.split('/')[0];\n return Promise.resolve({});\n } else {\n return this._super(route, args);\n }\n },\n /**\n * @private\n * @param {string} model\n * @param {Object} kwargs\n * @returns {Promise}\n */\n _mockReadGrid: function (model, kwargs) {\n var self = this;\n\n // various useful dates\n var gridAnchor = moment(kwargs.context.grid_anchor || this.currentDate);\n const currentDate = kwargs.context.grid_anchor ? moment(this.currentDate) : gridAnchor;\n var today = moment();\n var span = kwargs.range.span;\n var start = gridAnchor.clone().startOf(span === 'day' ? 'day' : span === 'week' ? 'isoWeek' : 'month');\n var end = gridAnchor.clone().endOf(span === 'day' ? 'day' : span === 'week' ? 'isoWeek' : 'month');\n var nextAnchor = gridAnchor.clone().add(1, span === 'day' ? 'day' : span === 'week' ? 'weeks' : 'month').format('YYYY-MM-DD');\n var prevAnchor = gridAnchor.clone().subtract(1, span === 'day' ? 'day' : span === 'week' ? 'weeks' : 'month').format('YYYY-MM-DD');\n\n // compute columns\n var columns = [];\n var current = start.clone().subtract(1, 'days');\n var unavailable = end.clone().subtract(2, 'days');\n\n while (!current.isSame(end, 'days')) {\n current.add(1, 'days');\n var dayStr = current.format('YYYY-MM-DD');\n var nextDayStr = current.clone().add(1, 'days').format('YYYY-MM-DD');\n columns.push({\n is_current: current.isSame(today),\n is_unavailable: unavailable.isSame(current, 'day'),\n domain: [\"&\", [\"date\", \">=\", dayStr], [\"date\", \"<\", nextDayStr]],\n values: {date: [dayStr + '/' + nextDayStr, current.format('ddd,\\nMMM\\u00a0DD')]}\n });\n }\n\n // compute rows\n var rows = [];\n var domain = [\n '&',\n [kwargs.col_field, '>=', start.format('YYYY-MM-DD')],\n [kwargs.col_field, '<=', end.format('YYYY-MM-DD')]\n ].concat(kwargs.domain);\n\n var groups = this._mockReadGroup(model, {\n domain: domain,\n fields: [kwargs.cell_field],\n groupby: [kwargs.row_fields[0]],\n });\n _.each(groups, function (group) {\n var groupValue = {};\n groupValue[kwargs.row_fields[0]] = group[kwargs.row_fields[0]];\n var groupDomain = ['&'].concat(domain).concat(group.__domain);\n if (kwargs.row_fields[1]) {\n var subGroups = self._mockReadGroup(model, {\n domain: groupDomain,\n fields: [kwargs.cell_field],\n groupby: [kwargs.row_fields[1]],\n });\n _.each(subGroups, function (subGroup) {\n var subGroupDomain = ['&'].concat(groupDomain, subGroup.__domain);\n var values = _.extend({}, groupValue);\n values[kwargs.row_fields[1]] = subGroup[kwargs.row_fields[1]] || false;\n rows.unshift({\n domain: subGroupDomain,\n values: values,\n });\n });\n } else {\n rows.unshift({\n domain: groupDomain,\n values: groupValue,\n });\n }\n });\n\n // generate cells\n var grid = [];\n _.each(rows, function (row) {\n var cells = [];\n _.each(columns, function (col) {\n var cellDomain = ['&'].concat(row.domain).concat(col.domain);\n var read_fields = kwargs.readonly_field ? [kwargs.cell_field, kwargs.readonly_field] : [kwargs.cell_field];\n var records = self._mockSearchReadController({\n model: model,\n domain: cellDomain,\n fields: read_fields,\n });\n var value = 0;\n _.each(records.records, function (rec) {\n value += rec[kwargs.cell_field];\n });\n var readonly_dict = {};\n readonly_dict[kwargs.readonly_field] = true;\n cells.push({\n size: records.length,\n value: value,\n is_current: col.is_current,\n is_unavailable: col.is_unavailable,\n readonly: _.isMatch(records.records[0], readonly_dict),\n domain: cellDomain,\n });\n\n });\n grid.push(cells);\n });\n\n return Promise.resolve({\n cols: columns,\n rows: rows,\n grid: grid,\n prev: {\n default_date: prevAnchor,\n grid_anchor: prevAnchor,\n },\n next: {\n default_date: nextAnchor,\n grid_anchor: nextAnchor,\n },\n initial: {\n default_date: currentDate.format('YYYY-MM-DD'),\n grid_anchor: currentDate.format('YYYY-MM-DD'),\n },\n });\n },\n _mockReadGridGrouped(model, kwargs) {\n const self = this;\n return this._mockReadGridDomain(model, kwargs).then(function (gridDomain) {\n gridDomain = gridDomain.concat(kwargs.domain || []);\n const groups = self._mockReadGroup(model, {\n domain: gridDomain,\n fields: [kwargs.section_field],\n groupby: [kwargs.section_field],\n });\n if (!groups.length) {\n return Promise.all([self._mockReadGrid(model, {\n row_fields: kwargs.row_fields,\n col_field: kwargs.col_field,\n cell_field: kwargs.cell_field,\n domain: kwargs.domain,\n range: kwargs.current_range,\n readonly_field: kwargs.readonly_field,\n context: kwargs.context,\n })]);\n } else {\n return Promise.all((groups || []).map((group) => {\n return self._mockReadGrid(model, {\n row_fields: kwargs.row_fields,\n col_field: kwargs.col_field,\n cell_field: kwargs.cell_field,\n domain: group.__domain,\n range: kwargs.current_range,\n readonly_field: kwargs.readonly_field,\n context: kwargs.context,\n }).then(function (result) {\n result.__label = group[kwargs.section_field];\n return result;\n });\n }));\n }\n });\n },\n /**\n * @TODO: this is not very generic but it works for the tests\n * @private\n * @returns {Promise}\n */\n _mockReadGridDomain: function (model, kwargs) {\n if (kwargs.context && kwargs.context.grid_anchor && kwargs.current_range && kwargs.current_range.span) {\n var gridAnchor = moment(kwargs.context.grid_anchor || this.currentDate);\n var span = kwargs.current_range.span;\n var start = gridAnchor.clone().startOf(span === 'day' ? 'day' : span === 'week' ? 'isoWeek' : 'month');\n var end = gridAnchor.clone().endOf(span === 'day' ? 'day' : span === 'week' ? 'isoWeek' : 'month');\n\n return Promise.resolve([\n '&',\n ['date', '>=', start.format('YYYY-MM-DD')],\n ['date', '<=', end.format('YYYY-MM-DD')],\n ]);\n }\n return Promise.resolve([\n '&',\n ['date', '>=', '2017-01-01'],\n ['date', '<=', '2017-01-31'],\n ]);\n },\n});\n\n});\n", "/** @odoo-module **/\n\nimport { createWebClient, getActionManagerServerData, doAction } from \"@web/../tests/webclient/helpers\";\nimport { click, getFixture } from \"@web/../tests/helpers/utils\";\n\nlet serverData;\nlet target;\n\nQUnit.module('WebMap Mobile', {\n beforeEach() {\n serverData = getActionManagerServerData();\n target = getFixture();\n Object.assign(serverData, {\n actions: {\n 1: {\n id: 1,\n name: 'Task Action 1',\n res_model: 'project.task',\n type: 'ir.actions.act_window',\n views: [[false, 'list'], [false, 'map'], [false, 'kanban'], [false, 'form']],\n },\n },\n views: {\n 'project.task,false,map': `\n \n \n `,\n 'project.task,false,list': '',\n 'project.task,false,kanban': `\n \n \n \n
\n \n
\n
\n
\n
`,\n 'project.task,false,form':\n `
\n \n \n \n
`,\n 'project.task,false,search': '',\n },\n models: {\n 'project.task': {\n fields: {\n display_name: { string: \"name\", type: \"char\" },\n sequence: { string: \"sequence\", type: \"integer\" },\n partner_id: {\n string: \"partner\",\n type: \"many2one\",\n relation: \"res.partner\",\n },\n },\n },\n },\n });\n },\n});\n\nQUnit.test(\"uses a Map(first mobile-friendly) view by default\", async function (assert) {\n const webClient = await createWebClient({ serverData });\n // should open Map(first mobile-friendly) view for action\n await doAction(webClient, 1);\n\n assert.containsNone(target, '.o_list_view');\n assert.containsNone(target, '.o_kanban_view');\n assert.containsOnce(target, '.o_map_view');\n\n});\n\nQUnit.test(\"use pin list container on mobile\", async function (assert) {\n const webClient = await createWebClient({ serverData });\n await doAction(webClient, 1);\n\n assert.containsOnce(target, \".o_pin_list_header .fa.fa-caret-left\");\n assert.containsNone(target, \".o_pin_list_header .fa.fa-caret-down\");\n\n await click(target, \".o_pin_list_header\");\n assert.containsOnce(\n target,\n \".o-sm-pin-list-container.h-100\",\n \"There should extend the pin list container\"\n );\n assert.containsOnce(target, \".o_pin_list_header .fa.fa-caret-down\");\n assert.containsNone(target, \".o_pin_list_header .fa.fa-caret-left\");\n\n await click(target, \".o_pin_list_header\");\n assert.containsOnce(target, \".o_pin_list_header .fa.fa-caret-left\");\n assert.containsNone(target, \".o_pin_list_header .fa.fa-caret-down\");\n assert.containsNone(\n target,\n \".o-sm-pin-list-container.h-100\",\n \"There should collapse the pin list container\"\n );\n});\n", "/* @preserve\n * Leaflet 1.4.0+Detached: 3337f36d2a2d2b33946779057619b31f674ff5dc.3337f36, a JS library for interactive maps. http://leafletjs.com\n * (c) 2010-2018 Vladimir Agafonkin, (c) 2010-2011 CloudMade\n */\n!function(t,i){\"object\"==typeof exports&&\"undefined\"!=typeof module?i(exports):\"function\"==typeof define&&define.amd?define([\"exports\"],i):i(t.L={})}(this,function(t){\"use strict\";function i(t){var i,e,n,o;for(e=1,n=arguments.length;e=0}function A(t,i,e,n){return\"touchstart\"===i?O(t,e,n):\"touchmove\"===i?W(t,e,n):\"touchend\"===i&&H(t,e,n),this}function I(t,i,e){var n=t[\"_leaflet_\"+i+e];return\"touchstart\"===i?t.removeEventListener(te,n,!1):\"touchmove\"===i?t.removeEventListener(ie,n,!1):\"touchend\"===i&&(t.removeEventListener(ee,n,!1),t.removeEventListener(ne,n,!1)),this}function O(t,i,n){var o=e(function(t){if(\"mouse\"!==t.pointerType&&t.MSPOINTER_TYPE_MOUSE&&t.pointerType!==t.MSPOINTER_TYPE_MOUSE){if(!(oe.indexOf(t.target.tagName)<0))return;Pt(t)}j(t,i)});t[\"_leaflet_touchstart\"+n]=o,t.addEventListener(te,o,!1),re||(document.documentElement.addEventListener(te,R,!0),document.documentElement.addEventListener(ie,N,!0),document.documentElement.addEventListener(ee,D,!0),document.documentElement.addEventListener(ne,D,!0),re=!0)}function R(t){se[t.pointerId]=t,ae++}function N(t){se[t.pointerId]&&(se[t.pointerId]=t)}function D(t){delete se[t.pointerId],ae--}function j(t,i){t.touches=[];for(var e in se)t.touches.push(se[e]);t.changedTouches=[t],i(t)}function W(t,i,e){var n=function(t){(t.pointerType!==t.MSPOINTER_TYPE_MOUSE&&\"mouse\"!==t.pointerType||0!==t.buttons)&&j(t,i)};t[\"_leaflet_touchmove\"+e]=n,t.addEventListener(ie,n,!1)}function H(t,i,e){var n=function(t){j(t,i)};t[\"_leaflet_touchend\"+e]=n,t.addEventListener(ee,n,!1),t.addEventListener(ne,n,!1)}function F(t,i,e){function n(t){var i;if(Vi){if(!bi||\"mouse\"===t.pointerType)return;i=ae}else i=t.touches.length;if(!(i>1)){var e=Date.now(),n=e-(s||e);r=t.touches?t.touches[0]:t,a=n>0&&n<=h,s=e}}function o(t){if(a&&!r.cancelBubble){if(Vi){if(!bi||\"mouse\"===t.pointerType)return;var e,n,o={};for(n in r)e=r[n],o[n]=e&&e.bind?e.bind(r):e;r=o}r.type=\"dblclick\",i(r),s=null}}var s,r,a=!1,h=250;return t[le+he+e]=n,t[le+ue+e]=o,t[le+\"dblclick\"+e]=i,t.addEventListener(he,n,!1),t.addEventListener(ue,o,!1),t.addEventListener(\"dblclick\",i,!1),this}function U(t,i){var e=t[le+he+i],n=t[le+ue+i],o=t[le+\"dblclick\"+i];return t.removeEventListener(he,e,!1),t.removeEventListener(ue,n,!1),bi||t.removeEventListener(\"dblclick\",o,!1),this}function V(t){return\"string\"==typeof t?document.getElementById(t):t}function q(t,i){var e=t.style[i]||t.currentStyle&&t.currentStyle[i];if((!e||\"auto\"===e)&&document.defaultView){var n=document.defaultView.getComputedStyle(t,null);e=n?n[i]:null}return\"auto\"===e?null:e}function G(t,i,e){var n=document.createElement(t);return n.className=i||\"\",e&&e.appendChild(n),n}function K(t){var i=t.parentNode;i&&i.removeChild(t)}function Y(t){for(;t.firstChild;)t.removeChild(t.firstChild)}function X(t){var i=t.parentNode;i&&i.lastChild!==t&&i.appendChild(t)}function J(t){var i=t.parentNode;i&&i.firstChild!==t&&i.insertBefore(t,i.firstChild)}function $(t,i){if(void 0!==t.classList)return t.classList.contains(i);var e=et(t);return e.length>0&&new RegExp(\"(^|\\\\s)\"+i+\"(\\\\s|$)\").test(e)}function Q(t,i){if(void 0!==t.classList)for(var e=u(i),n=0,o=e.length;n100&&n<500||t.target._simulatedClick&&!t._simulated?Lt(t):(ge=e,i(t))}function Zt(t,i){if(!i||!t.length)return t.slice();var e=i*i;return t=At(t,e),t=kt(t,e)}function Et(t,i,e){return Math.sqrt(Dt(t,i,e,!0))}function kt(t,i){var e=t.length,n=new(typeof Uint8Array!=void 0+\"\"?Uint8Array:Array)(e);n[0]=n[e-1]=1,Bt(t,n,i,0,e-1);var o,s=[];for(o=0;oh&&(s=r,h=a);h>e&&(i[s]=1,Bt(t,i,e,n,s),Bt(t,i,e,s,o))}function At(t,i){for(var e=[t[0]],n=1,o=0,s=t.length;ni&&(e.push(t[n]),o=n);return oi.max.x&&(e|=2),t.yi.max.y&&(e|=8),e}function Nt(t,i){var e=i.x-t.x,n=i.y-t.y;return e*e+n*n}function Dt(t,i,e,n){var o,s=i.x,r=i.y,a=e.x-s,h=e.y-r,u=a*a+h*h;return u>0&&((o=((t.x-s)*a+(t.y-r)*h)/u)>1?(s=e.x,r=e.y):o>0&&(s+=a*o,r+=h*o)),a=t.x-s,h=t.y-r,n?a*a+h*h:new x(s,r)}function jt(t){return!oi(t[0])||\"object\"!=typeof t[0][0]&&void 0!==t[0][0]}function Wt(t){return console.warn(\"Deprecated use of _flat, please use L.LineUtil.isFlat instead.\"),jt(t)}function Ht(t,i,e){var n,o,s,r,a,h,u,l,c,_=[1,4,2,8];for(o=0,u=t.length;o0?Math.floor(t):Math.ceil(t)};x.prototype={clone:function(){return new x(this.x,this.y)},add:function(t){return this.clone()._add(w(t))},_add:function(t){return this.x+=t.x,this.y+=t.y,this},subtract:function(t){return this.clone()._subtract(w(t))},_subtract:function(t){return this.x-=t.x,this.y-=t.y,this},divideBy:function(t){return this.clone()._divideBy(t)},_divideBy:function(t){return this.x/=t,this.y/=t,this},multiplyBy:function(t){return this.clone()._multiplyBy(t)},_multiplyBy:function(t){return this.x*=t,this.y*=t,this},scaleBy:function(t){return new x(this.x*t.x,this.y*t.y)},unscaleBy:function(t){return new x(this.x/t.x,this.y/t.y)},round:function(){return this.clone()._round()},_round:function(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this},floor:function(){return this.clone()._floor()},_floor:function(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this},ceil:function(){return this.clone()._ceil()},_ceil:function(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this},trunc:function(){return this.clone()._trunc()},_trunc:function(){return this.x=_i(this.x),this.y=_i(this.y),this},distanceTo:function(t){var i=(t=w(t)).x-this.x,e=t.y-this.y;return Math.sqrt(i*i+e*e)},equals:function(t){return(t=w(t)).x===this.x&&t.y===this.y},contains:function(t){return t=w(t),Math.abs(t.x)<=Math.abs(this.x)&&Math.abs(t.y)<=Math.abs(this.y)},toString:function(){return\"Point(\"+a(this.x)+\", \"+a(this.y)+\")\"}},P.prototype={extend:function(t){return t=w(t),this.min||this.max?(this.min.x=Math.min(t.x,this.min.x),this.max.x=Math.max(t.x,this.max.x),this.min.y=Math.min(t.y,this.min.y),this.max.y=Math.max(t.y,this.max.y)):(this.min=t.clone(),this.max=t.clone()),this},getCenter:function(t){return new x((this.min.x+this.max.x)/2,(this.min.y+this.max.y)/2,t)},getBottomLeft:function(){return new x(this.min.x,this.max.y)},getTopRight:function(){return new x(this.max.x,this.min.y)},getTopLeft:function(){return this.min},getBottomRight:function(){return this.max},getSize:function(){return this.max.subtract(this.min)},contains:function(t){var i,e;return(t=\"number\"==typeof t[0]||t instanceof x?w(t):b(t))instanceof P?(i=t.min,e=t.max):i=e=t,i.x>=this.min.x&&e.x<=this.max.x&&i.y>=this.min.y&&e.y<=this.max.y},intersects:function(t){t=b(t);var i=this.min,e=this.max,n=t.min,o=t.max,s=o.x>=i.x&&n.x<=e.x,r=o.y>=i.y&&n.y<=e.y;return s&&r},overlaps:function(t){t=b(t);var i=this.min,e=this.max,n=t.min,o=t.max,s=o.x>i.x&&n.xi.y&&n.y=n.lat&&e.lat<=o.lat&&i.lng>=n.lng&&e.lng<=o.lng},intersects:function(t){t=z(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),o=t.getNorthEast(),s=o.lat>=i.lat&&n.lat<=e.lat,r=o.lng>=i.lng&&n.lng<=e.lng;return s&&r},overlaps:function(t){t=z(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),o=t.getNorthEast(),s=o.lat>i.lat&&n.lati.lng&&n.lng1,Xi=!!document.createElement(\"canvas\").getContext,Ji=!(!document.createElementNS||!E(\"svg\").createSVGRect),$i=!Ji&&function(){try{var t=document.createElement(\"div\");t.innerHTML='';var i=t.firstChild;return i.style.behavior=\"url(#default#VML)\",i&&\"object\"==typeof i.adj}catch(t){return!1}}(),Qi=(Object.freeze||Object)({ie:Pi,ielt9:Li,edge:bi,webkit:Ti,android:zi,android23:Mi,androidStock:Si,opera:Zi,chrome:Ei,gecko:ki,safari:Bi,phantom:Ai,opera12:Ii,win:Oi,ie3d:Ri,webkit3d:Ni,gecko3d:Di,any3d:ji,mobile:Wi,mobileWebkit:Hi,mobileWebkit3d:Fi,msPointer:Ui,pointer:Vi,touch:qi,mobileOpera:Gi,mobileGecko:Ki,retina:Yi,canvas:Xi,svg:Ji,vml:$i}),te=Ui?\"MSPointerDown\":\"pointerdown\",ie=Ui?\"MSPointerMove\":\"pointermove\",ee=Ui?\"MSPointerUp\":\"pointerup\",ne=Ui?\"MSPointerCancel\":\"pointercancel\",oe=[\"INPUT\",\"SELECT\",\"OPTION\"],se={},re=!1,ae=0,he=Ui?\"MSPointerDown\":Vi?\"pointerdown\":\"touchstart\",ue=Ui?\"MSPointerUp\":Vi?\"pointerup\":\"touchend\",le=\"_leaflet_\",ce=st([\"transform\",\"webkitTransform\",\"OTransform\",\"MozTransform\",\"msTransform\"]),_e=st([\"webkitTransition\",\"transition\",\"OTransition\",\"MozTransition\",\"msTransition\"]),de=\"webkitTransition\"===_e||\"OTransition\"===_e?_e+\"End\":\"transitionend\";if(\"onselectstart\"in document)fi=function(){mt(window,\"selectstart\",Pt)},gi=function(){ft(window,\"selectstart\",Pt)};else{var pe=st([\"userSelect\",\"WebkitUserSelect\",\"OUserSelect\",\"MozUserSelect\",\"msUserSelect\"]);fi=function(){if(pe){var t=document.documentElement.style;vi=t[pe],t[pe]=\"none\"}},gi=function(){pe&&(document.documentElement.style[pe]=vi,vi=void 0)}}var me,fe,ge,ve=(Object.freeze||Object)({TRANSFORM:ce,TRANSITION:_e,TRANSITION_END:de,get:V,getStyle:q,create:G,remove:K,empty:Y,toFront:X,toBack:J,hasClass:$,addClass:Q,removeClass:tt,setClass:it,getClass:et,setOpacity:nt,testProp:st,setTransform:rt,setPosition:at,getPosition:ht,disableTextSelection:fi,enableTextSelection:gi,disableImageDrag:ut,enableImageDrag:lt,preventOutline:ct,restoreOutline:_t,getSizedParentNode:dt,getScale:pt}),ye=\"_leaflet_events\",xe=Oi&&Ei?2*window.devicePixelRatio:ki?window.devicePixelRatio:1,we={},Pe=(Object.freeze||Object)({on:mt,off:ft,stopPropagation:yt,disableScrollPropagation:xt,disableClickPropagation:wt,preventDefault:Pt,stop:Lt,getMousePosition:bt,getWheelDelta:Tt,fakeStop:zt,skipped:Mt,isExternalTarget:Ct,addListener:mt,removeListener:ft}),Le=ci.extend({run:function(t,i,e,n){this.stop(),this._el=t,this._inProgress=!0,this._duration=e||.25,this._easeOutPower=1/Math.max(n||.5,.2),this._startPos=ht(t),this._offset=i.subtract(this._startPos),this._startTime=+new Date,this.fire(\"start\"),this._animate()},stop:function(){this._inProgress&&(this._step(!0),this._complete())},_animate:function(){this._animId=f(this._animate,this),this._step()},_step:function(t){var i=+new Date-this._startTime,e=1e3*this._duration;ithis.options.maxZoom)?this.setZoom(t):this},panInsideBounds:function(t,i){this._enforcingBounds=!0;var e=this.getCenter(),n=this._limitCenter(e,this._zoom,z(t));return e.equals(n)||this.panTo(n,i),this._enforcingBounds=!1,this},panInside:function(t,i){var e=w((i=i||{}).paddingTopLeft||i.padding||[0,0]),n=w(i.paddingBottomRight||i.padding||[0,0]),o=this.getCenter(),s=this.project(o),r=this.project(t),a=this.getPixelBounds(),h=a.getSize().divideBy(2),u=b([a.min.add(e),a.max.subtract(n)]);if(!u.contains(r)){this._enforcingBounds=!0;var l=s.subtract(r),c=w(r.x+l.x,r.y+l.y);(r.xu.max.x)&&(c.x=s.x-l.x,l.x>0?c.x+=h.x-e.x:c.x-=h.x-n.x),(r.yu.max.y)&&(c.y=s.y-l.y,l.y>0?c.y+=h.y-e.y:c.y-=h.y-n.y),this.panTo(this.unproject(c),i),this._enforcingBounds=!1}return this},invalidateSize:function(t){if(!this._loaded)return this;t=i({animate:!1,pan:!0},!0===t?{animate:!0}:t);var n=this.getSize();this._sizeChanged=!0,this._lastCenter=null;var o=this.getSize(),s=n.divideBy(2).round(),r=o.divideBy(2).round(),a=s.subtract(r);return a.x||a.y?(t.animate&&t.pan?this.panBy(a):(t.pan&&this._rawPanBy(a),this.fire(\"move\"),t.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(e(this.fire,this,\"moveend\"),200)):this.fire(\"moveend\")),this.fire(\"resize\",{oldSize:n,newSize:o})):this},stop:function(){return this.setZoom(this._limitZoom(this._zoom)),this.options.zoomSnap||this.fire(\"viewreset\"),this._stop()},locate:function(t){if(t=this._locateOptions=i({timeout:1e4,watch:!1},t),!(\"geolocation\"in navigator))return this._handleGeolocationError({code:0,message:\"Geolocation not supported.\"}),this;var n=e(this._handleGeolocationResponse,this),o=e(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(n,o,t):navigator.geolocation.getCurrentPosition(n,o,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var i=t.code,e=t.message||(1===i?\"permission denied\":2===i?\"position unavailable\":\"timeout\");this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire(\"locationerror\",{code:i,message:\"Geolocation error: \"+e+\".\"})},_handleGeolocationResponse:function(t){var i=new M(t.coords.latitude,t.coords.longitude),e=i.toBounds(2*t.coords.accuracy),n=this._locateOptions;if(n.setView){var o=this.getBoundsZoom(e);this.setView(i,n.maxZoom?Math.min(o,n.maxZoom):o)}var s={latlng:i,bounds:e,timestamp:t.timestamp};for(var r in t.coords)\"number\"==typeof t.coords[r]&&(s[r]=t.coords[r]);this.fire(\"locationfound\",s)},addHandler:function(t,i){if(!i)return this;var e=this[t]=new i(this);return this._handlers.push(e),this.options[t]&&e.enable(),this},remove:function(){if(this._initEvents(!0),this._containerId!==this._container._leaflet_id)throw new Error(\"Map container is being reused by another instance\");try{delete this._container._leaflet_id,delete this._containerId}catch(t){this._container._leaflet_id=void 0,this._containerId=void 0}void 0!==this._locationWatchId&&this.stopLocate(),this._stop(),K(this._mapPane),this._clearControlPos&&this._clearControlPos(),this._resizeRequest&&(g(this._resizeRequest),this._resizeRequest=null),this._clearHandlers(),this._loaded&&this.fire(\"unload\");var t;for(t in this._layers)this._layers[t].remove();for(t in this._panes)K(this._panes[t]);return this._layers=[],this._panes=[],delete this._mapPane,delete this._renderer,this},createPane:function(t,i){var e=G(\"div\",\"leaflet-pane\"+(t?\" leaflet-\"+t.replace(\"Pane\",\"\")+\"-pane\":\"\"),i||this._mapPane);return t&&(this._panes[t]=e),e},getCenter:function(){return this._checkIfLoaded(),this._lastCenter&&!this._moved()?this._lastCenter:this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds();return new T(this.unproject(t.getBottomLeft()),this.unproject(t.getTopRight()))},getMinZoom:function(){return void 0===this.options.minZoom?this._layersMinZoom||0:this.options.minZoom},getMaxZoom:function(){return void 0===this.options.maxZoom?void 0===this._layersMaxZoom?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(t,i,e){t=z(t),e=w(e||[0,0]);var n=this.getZoom()||0,o=this.getMinZoom(),s=this.getMaxZoom(),r=t.getNorthWest(),a=t.getSouthEast(),h=this.getSize().subtract(e),u=b(this.project(a,n),this.project(r,n)).getSize(),l=ji?this.options.zoomSnap:1,c=h.x/u.x,_=h.y/u.y,d=i?Math.max(c,_):Math.min(c,_);return n=this.getScaleZoom(d,n),l&&(n=Math.round(n/(l/100))*(l/100),n=i?Math.ceil(n/l)*l:Math.floor(n/l)*l),Math.max(o,Math.min(s,n))},getSize:function(){return this._size&&!this._sizeChanged||(this._size=new x(this._container.clientWidth||0,this._container.clientHeight||0),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(t,i){var e=this._getTopLeftPoint(t,i);return new P(e,e.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._pixelOrigin},getPixelWorldBounds:function(t){return this.options.crs.getProjectedBounds(void 0===t?this.getZoom():t)},getPane:function(t){return\"string\"==typeof t?this._panes[t]:t},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t,i){var e=this.options.crs;return i=void 0===i?this._zoom:i,e.scale(t)/e.scale(i)},getScaleZoom:function(t,i){var e=this.options.crs;i=void 0===i?this._zoom:i;var n=e.zoom(t*e.scale(i));return isNaN(n)?1/0:n},project:function(t,i){return i=void 0===i?this._zoom:i,this.options.crs.latLngToPoint(C(t),i)},unproject:function(t,i){return i=void 0===i?this._zoom:i,this.options.crs.pointToLatLng(w(t),i)},layerPointToLatLng:function(t){var i=w(t).add(this.getPixelOrigin());return this.unproject(i)},latLngToLayerPoint:function(t){return this.project(C(t))._round()._subtract(this.getPixelOrigin())},wrapLatLng:function(t){return this.options.crs.wrapLatLng(C(t))},wrapLatLngBounds:function(t){return this.options.crs.wrapLatLngBounds(z(t))},distance:function(t,i){return this.options.crs.distance(C(t),C(i))},containerPointToLayerPoint:function(t){return w(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return w(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){var i=this.containerPointToLayerPoint(w(t));return this.layerPointToLatLng(i)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(C(t)))},mouseEventToContainerPoint:function(t){return bt(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){var i=this._container=V(t);if(!i)throw new Error(\"Map container not found.\");if(i._leaflet_id)throw new Error(\"Map container is already initialized.\");mt(i,\"scroll\",this._onScroll,this),this._containerId=n(i)},_initLayout:function(){var t=this._container;this._fadeAnimated=this.options.fadeAnimation&&ji,Q(t,\"leaflet-container\"+(qi?\" leaflet-touch\":\"\")+(Yi?\" leaflet-retina\":\"\")+(Li?\" leaflet-oldie\":\"\")+(Bi?\" leaflet-safari\":\"\")+(this._fadeAnimated?\" leaflet-fade-anim\":\"\"));var i=q(t,\"position\");\"absolute\"!==i&&\"relative\"!==i&&\"fixed\"!==i&&(t.style.position=\"relative\"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._paneRenderers={},this._mapPane=this.createPane(\"mapPane\",this._container),at(this._mapPane,new x(0,0)),this.createPane(\"tilePane\"),this.createPane(\"shadowPane\"),this.createPane(\"overlayPane\"),this.createPane(\"markerPane\"),this.createPane(\"tooltipPane\"),this.createPane(\"popupPane\"),this.options.markerZoomAnimation||(Q(t.markerPane,\"leaflet-zoom-hide\"),Q(t.shadowPane,\"leaflet-zoom-hide\"))},_resetView:function(t,i){at(this._mapPane,new x(0,0));var e=!this._loaded;this._loaded=!0,i=this._limitZoom(i),this.fire(\"viewprereset\");var n=this._zoom!==i;this._moveStart(n,!1)._move(t,i)._moveEnd(n),this.fire(\"viewreset\"),e&&this.fire(\"load\")},_moveStart:function(t,i){return t&&this.fire(\"zoomstart\"),i||this.fire(\"movestart\"),this},_move:function(t,i,e){void 0===i&&(i=this._zoom);var n=this._zoom!==i;return this._zoom=i,this._lastCenter=t,this._pixelOrigin=this._getNewPixelOrigin(t),(n||e&&e.pinch)&&this.fire(\"zoom\",e),this.fire(\"move\",e)},_moveEnd:function(t){return t&&this.fire(\"zoomend\"),this.fire(\"moveend\")},_stop:function(){return g(this._flyToFrame),this._panAnim&&this._panAnim.stop(),this},_rawPanBy:function(t){at(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_panInsideMaxBounds:function(){this._enforcingBounds||this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error(\"Set map center and zoom first.\")},_initEvents:function(t){this._targets={},this._targets[n(this._container)]=this;var i=t?ft:mt;i(this._container,\"click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress\",this._handleDOMEvent,this),this.options.trackResize&&i(window,\"resize\",this._onResize,this),ji&&this.options.transform3DLimit&&(t?this.off:this.on).call(this,\"moveend\",this._onMoveEnd)},_onResize:function(){g(this._resizeRequest),this._resizeRequest=f(function(){this.invalidateSize({debounceMoveend:!0})},this)},_onScroll:function(){this._container.scrollTop=0,this._container.scrollLeft=0},_onMoveEnd:function(){var t=this._getMapPanePos();Math.max(Math.abs(t.x),Math.abs(t.y))>=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,i){for(var e,o=[],s=\"mouseout\"===i||\"mouseover\"===i,r=t.target||t.srcElement,a=!1;r;){if((e=this._targets[n(r)])&&(\"click\"===i||\"preclick\"===i)&&!t._simulated&&this._draggableMoved(e)){a=!0;break}if(e&&e.listens(i,!0)){if(s&&!Ct(r,t))break;if(o.push(e),s)break}if(r===this._container)break;r=r.parentNode}return o.length||a||s||!Ct(r,t)||(o=[this]),o},_handleDOMEvent:function(t){if(this._loaded&&!Mt(t)){var i=t.type;\"mousedown\"!==i&&\"keypress\"!==i||ct(t.target||t.srcElement),this._fireDOMEvent(t,i)}},_mouseEvents:[\"click\",\"dblclick\",\"mouseover\",\"mouseout\",\"contextmenu\"],_fireDOMEvent:function(t,e,n){if(\"click\"===t.type){var o=i({},t);o.type=\"preclick\",this._fireDOMEvent(o,o.type,n)}if(!t._stopped&&(n=(n||[]).concat(this._findEventTargets(t,e))).length){var s=n[0];\"contextmenu\"===e&&s.listens(e,!0)&&Pt(t);var r={originalEvent:t};if(\"keypress\"!==t.type){var a=s.getLatLng&&(!s._radius||s._radius<=10);r.containerPoint=a?this.latLngToContainerPoint(s.getLatLng()):this.mouseEventToContainerPoint(t),r.layerPoint=this.containerPointToLayerPoint(r.containerPoint),r.latlng=a?s.getLatLng():this.layerPointToLatLng(r.layerPoint)}for(var h=0;h0?Math.round(t-i)/2:Math.max(0,Math.ceil(t))-Math.max(0,Math.floor(i))},_limitZoom:function(t){var i=this.getMinZoom(),e=this.getMaxZoom(),n=ji?this.options.zoomSnap:1;return n&&(t=Math.round(t/n)*n),Math.max(i,Math.min(e,t))},_onPanTransitionStep:function(){this.fire(\"move\")},_onPanTransitionEnd:function(){tt(this._mapPane,\"leaflet-pan-anim\"),this.fire(\"moveend\")},_tryAnimatedPan:function(t,i){var e=this._getCenterOffset(t)._trunc();return!(!0!==(i&&i.animate)&&!this.getSize().contains(e))&&(this.panBy(e,i),!0)},_createAnimProxy:function(){var t=this._proxy=G(\"div\",\"leaflet-proxy leaflet-zoom-animated\");this._panes.mapPane.appendChild(t),this.on(\"zoomanim\",function(t){var i=ce,e=this._proxy.style[i];rt(this._proxy,this.project(t.center,t.zoom),this.getZoomScale(t.zoom,1)),e===this._proxy.style[i]&&this._animatingZoom&&this._onZoomTransitionEnd()},this),this.on(\"load moveend\",function(){var t=this.getCenter(),i=this.getZoom();rt(this._proxy,this.project(t,i),this.getZoomScale(i,1))},this),this._on(\"unload\",this._destroyAnimProxy,this)},_destroyAnimProxy:function(){K(this._proxy),delete this._proxy},_catchTransitionEnd:function(t){this._animatingZoom&&t.propertyName.indexOf(\"transform\")>=0&&this._onZoomTransitionEnd()},_nothingToAnimate:function(){return!this._container.getElementsByClassName(\"leaflet-zoom-animated\").length},_tryAnimatedZoom:function(t,i,e){if(this._animatingZoom)return!0;if(e=e||{},!this._zoomAnimated||!1===e.animate||this._nothingToAnimate()||Math.abs(i-this._zoom)>this.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(i),o=this._getCenterOffset(t)._divideBy(1-1/n);return!(!0!==e.animate&&!this.getSize().contains(o))&&(f(function(){this._moveStart(!0,!1)._animateZoom(t,i,!0)},this),!0)},_animateZoom:function(t,i,n,o){this._mapPane&&(n&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=i,Q(this._mapPane,\"leaflet-zoom-anim\")),this.fire(\"zoomanim\",{center:t,zoom:i,noUpdate:o}),setTimeout(e(this._onZoomTransitionEnd,this),250))},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._mapPane&&tt(this._mapPane,\"leaflet-zoom-anim\"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom),f(function(){this._moveEnd(!0)},this))}}),Te=v.extend({options:{position:\"topright\"},initialize:function(t){l(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var i=this._map;return i&&i.removeControl(this),this.options.position=t,i&&i.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this.remove(),this._map=t;var i=this._container=this.onAdd(t),e=this.getPosition(),n=t._controlCorners[e];return Q(i,\"leaflet-control\"),-1!==e.indexOf(\"bottom\")?n.insertBefore(i,n.firstChild):n.appendChild(i),this},remove:function(){return this._map?(K(this._container),this.onRemove&&this.onRemove(this._map),this._map=null,this):this},_refocusOnMap:function(t){this._map&&t&&t.screenX>0&&t.screenY>0&&this._map.getContainer().focus()}}),ze=function(t){return new Te(t)};be.include({addControl:function(t){return t.addTo(this),this},removeControl:function(t){return t.remove(),this},_initControlPos:function(){function t(t,o){var s=e+t+\" \"+e+o;i[t+o]=G(\"div\",s,n)}var i=this._controlCorners={},e=\"leaflet-\",n=this._controlContainer=G(\"div\",e+\"control-container\",this._container);t(\"top\",\"left\"),t(\"top\",\"right\"),t(\"bottom\",\"left\"),t(\"bottom\",\"right\")},_clearControlPos:function(){for(var t in this._controlCorners)K(this._controlCorners[t]);K(this._controlContainer),delete this._controlCorners,delete this._controlContainer}});var Me=Te.extend({options:{collapsed:!0,position:\"topright\",autoZIndex:!0,hideSingleBase:!1,sortLayers:!1,sortFunction:function(t,i,e,n){return e1,this._baseLayersList.style.display=t?\"\":\"none\"),this._separator.style.display=i&&t?\"\":\"none\",this},_onLayerChange:function(t){this._handlingClick||this._update();var i=this._getLayer(n(t.target)),e=i.overlay?\"add\"===t.type?\"overlayadd\":\"overlayremove\":\"add\"===t.type?\"baselayerchange\":null;e&&this._map.fire(e,i)},_createRadioElement:function(t,i){var e='\",n=document.createElement(\"div\");return n.innerHTML=e,n.firstChild},_addItem:function(t){var i,e=document.createElement(\"label\"),o=this._map.hasLayer(t.layer);t.overlay?((i=document.createElement(\"input\")).type=\"checkbox\",i.className=\"leaflet-control-layers-selector\",i.defaultChecked=o):i=this._createRadioElement(\"leaflet-base-layers\",o),this._layerControlInputs.push(i),i.layerId=n(t.layer),mt(i,\"click\",this._onInputClick,this);var s=document.createElement(\"span\");s.innerHTML=\" \"+t.name;var r=document.createElement(\"div\");return e.appendChild(r),r.appendChild(i),r.appendChild(s),(t.overlay?this._overlaysList:this._baseLayersList).appendChild(e),this._checkDisabledLayers(),e},_onInputClick:function(){var t,i,e=this._layerControlInputs,n=[],o=[];this._handlingClick=!0;for(var s=e.length-1;s>=0;s--)t=e[s],i=this._getLayer(t.layerId).layer,t.checked?n.push(i):t.checked||o.push(i);for(s=0;s=0;o--)t=e[o],i=this._getLayer(t.layerId).layer,t.disabled=void 0!==i.options.minZoom&&ni.options.maxZoom},_expandIfNotCollapsed:function(){return this._map&&!this.options.collapsed&&this.expand(),this},_expand:function(){return this.expand()},_collapse:function(){return this.collapse()}}),Ce=Te.extend({options:{position:\"topleft\",zoomInText:\"+\",zoomInTitle:\"Zoom in\",zoomOutText:\"−\",zoomOutTitle:\"Zoom out\"},onAdd:function(t){var i=\"leaflet-control-zoom\",e=G(\"div\",i+\" leaflet-bar\"),n=this.options;return this._zoomInButton=this._createButton(n.zoomInText,n.zoomInTitle,i+\"-in\",e,this._zoomIn),this._zoomOutButton=this._createButton(n.zoomOutText,n.zoomOutTitle,i+\"-out\",e,this._zoomOut),this._updateDisabled(),t.on(\"zoomend zoomlevelschange\",this._updateDisabled,this),e},onRemove:function(t){t.off(\"zoomend zoomlevelschange\",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoomthis._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,i,e,n,o){var s=G(\"a\",e,n);return s.innerHTML=t,s.href=\"#\",s.title=i,s.setAttribute(\"role\",\"button\"),s.setAttribute(\"aria-label\",i),wt(s),mt(s,\"click\",Lt),mt(s,\"click\",o,this),mt(s,\"click\",this._refocusOnMap,this),s},_updateDisabled:function(){var t=this._map,i=\"leaflet-disabled\";tt(this._zoomInButton,i),tt(this._zoomOutButton,i),(this._disabled||t._zoom===t.getMinZoom())&&Q(this._zoomOutButton,i),(this._disabled||t._zoom===t.getMaxZoom())&&Q(this._zoomInButton,i)}});be.mergeOptions({zoomControl:!0}),be.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new Ce,this.addControl(this.zoomControl))});var Se=Te.extend({options:{position:\"bottomleft\",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var i=G(\"div\",\"leaflet-control-scale\"),e=this.options;return this._addScales(e,\"leaflet-control-scale-line\",i),t.on(e.updateWhenIdle?\"moveend\":\"move\",this._update,this),t.whenReady(this._update,this),i},onRemove:function(t){t.off(this.options.updateWhenIdle?\"moveend\":\"move\",this._update,this)},_addScales:function(t,i,e){t.metric&&(this._mScale=G(\"div\",i,e)),t.imperial&&(this._iScale=G(\"div\",i,e))},_update:function(){var t=this._map,i=t.getSize().y/2,e=t.distance(t.containerPointToLatLng([0,i]),t.containerPointToLatLng([this.options.maxWidth,i]));this._updateScales(e)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var i=this._getRoundNum(t),e=i<1e3?i+\" m\":i/1e3+\" km\";this._updateScale(this._mScale,e,i/t)},_updateImperial:function(t){var i,e,n,o=3.2808399*t;o>5280?(i=o/5280,e=this._getRoundNum(i),this._updateScale(this._iScale,e+\" mi\",e/i)):(n=this._getRoundNum(o),this._updateScale(this._iScale,n+\" ft\",n/o))},_updateScale:function(t,i,e){t.style.width=Math.round(this.options.maxWidth*e)+\"px\",t.innerHTML=i},_getRoundNum:function(t){var i=Math.pow(10,(Math.floor(t)+\"\").length-1),e=t/i;return e=e>=10?10:e>=5?5:e>=3?3:e>=2?2:1,i*e}}),Ze=Te.extend({options:{position:\"bottomright\",prefix:'Leaflet'},initialize:function(t){l(this,t),this._attributions={}},onAdd:function(t){t.attributionControl=this,this._container=G(\"div\",\"leaflet-control-attribution\"),wt(this._container);for(var i in t._layers)t._layers[i].getAttribution&&this.addAttribution(t._layers[i].getAttribution());return this._update(),this._container},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t?(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update(),this):this},removeAttribution:function(t){return t?(this._attributions[t]&&(this._attributions[t]--,this._update()),this):this},_update:function(){if(this._map){var t=[];for(var i in this._attributions)this._attributions[i]&&t.push(i);var e=[];this.options.prefix&&e.push(this.options.prefix),t.length&&e.push(t.join(\", \")),this._container.innerHTML=e.join(\" | \")}}});be.mergeOptions({attributionControl:!0}),be.addInitHook(function(){this.options.attributionControl&&(new Ze).addTo(this)});Te.Layers=Me,Te.Zoom=Ce,Te.Scale=Se,Te.Attribution=Ze,ze.layers=function(t,i,e){return new Me(t,i,e)},ze.zoom=function(t){return new Ce(t)},ze.scale=function(t){return new Se(t)},ze.attribution=function(t){return new Ze(t)};var Ee=v.extend({initialize:function(t){this._map=t},enable:function(){return this._enabled?this:(this._enabled=!0,this.addHooks(),this)},disable:function(){return this._enabled?(this._enabled=!1,this.removeHooks(),this):this},enabled:function(){return!!this._enabled}});Ee.addTo=function(t,i){return t.addHandler(i,this),this};var ke,Be={Events:li},Ae=qi?\"touchstart mousedown\":\"mousedown\",Ie={mousedown:\"mouseup\",touchstart:\"touchend\",pointerdown:\"touchend\",MSPointerDown:\"touchend\"},Oe={mousedown:\"mousemove\",touchstart:\"touchmove\",pointerdown:\"touchmove\",MSPointerDown:\"touchmove\"},Re=ci.extend({options:{clickTolerance:3},initialize:function(t,i,e,n){l(this,n),this._element=t,this._dragStartTarget=i||t,this._preventOutline=e},enable:function(){this._enabled||(mt(this._dragStartTarget,Ae,this._onDown,this),this._enabled=!0)},disable:function(){this._enabled&&(Re._dragging===this&&this.finishDrag(),ft(this._dragStartTarget,Ae,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(t){if(!t._simulated&&this._enabled&&(this._moved=!1,!$(this._element,\"leaflet-zoom-anim\")&&!(Re._dragging||t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||(Re._dragging=this,this._preventOutline&&ct(this._element),ut(),fi(),this._moving)))){this.fire(\"down\");var i=t.touches?t.touches[0]:t,e=dt(this._element);this._startPoint=new x(i.clientX,i.clientY),this._parentScale=pt(e),mt(document,Oe[t.type],this._onMove,this),mt(document,Ie[t.type],this._onUp,this)}},_onMove:function(t){if(!t._simulated&&this._enabled)if(t.touches&&t.touches.length>1)this._moved=!0;else{var i=t.touches&&1===t.touches.length?t.touches[0]:t,e=new x(i.clientX,i.clientY)._subtract(this._startPoint);(e.x||e.y)&&(Math.abs(e.x)+Math.abs(e.y)1e-7;h++)i=s*Math.sin(a),i=Math.pow((1-i)/(1+i),s/2),a+=u=Math.PI/2-2*Math.atan(r*i)-a;return new M(a*e,t.x*e/n)}},He=(Object.freeze||Object)({LonLat:je,Mercator:We,SphericalMercator:mi}),Fe=i({},pi,{code:\"EPSG:3395\",projection:We,transformation:function(){var t=.5/(Math.PI*We.R);return Z(t,.5,-t,.5)}()}),Ue=i({},pi,{code:\"EPSG:4326\",projection:je,transformation:Z(1/180,1,-1/180,.5)}),Ve=i({},di,{projection:je,transformation:Z(1,0,-1,0),scale:function(t){return Math.pow(2,t)},zoom:function(t){return Math.log(t)/Math.LN2},distance:function(t,i){var e=i.lng-t.lng,n=i.lat-t.lat;return Math.sqrt(e*e+n*n)},infinite:!0});di.Earth=pi,di.EPSG3395=Fe,di.EPSG3857=yi,di.EPSG900913=xi,di.EPSG4326=Ue,di.Simple=Ve;var qe=ci.extend({options:{pane:\"overlayPane\",attribution:null,bubblingMouseEvents:!0},addTo:function(t){return t.addLayer(this),this},remove:function(){return this.removeFrom(this._map||this._mapToAdd)},removeFrom:function(t){return t&&t.removeLayer(this),this},getPane:function(t){return this._map.getPane(t?this.options[t]||t:this.options.pane)},addInteractiveTarget:function(t){return this._map._targets[n(t)]=this,this},removeInteractiveTarget:function(t){return delete this._map._targets[n(t)],this},getAttribution:function(){return this.options.attribution},_layerAdd:function(t){var i=t.target;if(i.hasLayer(this)){if(this._map=i,this._zoomAnimated=i._zoomAnimated,this.getEvents){var e=this.getEvents();i.on(e,this),this.once(\"remove\",function(){i.off(e,this)},this)}this.onAdd(i),this.getAttribution&&i.attributionControl&&i.attributionControl.addAttribution(this.getAttribution()),this.fire(\"add\"),i.fire(\"layeradd\",{layer:this})}}});be.include({addLayer:function(t){if(!t._layerAdd)throw new Error(\"The provided object is not a Layer.\");var i=n(t);return this._layers[i]?this:(this._layers[i]=t,t._mapToAdd=this,t.beforeAdd&&t.beforeAdd(this),this.whenReady(t._layerAdd,t),this)},removeLayer:function(t){var i=n(t);return this._layers[i]?(this._loaded&&t.onRemove(this),t.getAttribution&&this.attributionControl&&this.attributionControl.removeAttribution(t.getAttribution()),delete this._layers[i],this._loaded&&(this.fire(\"layerremove\",{layer:t}),t.fire(\"remove\")),t._map=t._mapToAdd=null,this):this},hasLayer:function(t){return!!t&&n(t)in this._layers},eachLayer:function(t,i){for(var e in this._layers)t.call(i,this._layers[e]);return this},_addLayers:function(t){for(var i=0,e=(t=t?oi(t)?t:[t]:[]).length;ithis._layersMaxZoom&&this.setZoom(this._layersMaxZoom),void 0===this.options.minZoom&&this._layersMinZoom&&this.getZoom()i)return r=(n-i)/e,this._map.layerPointToLatLng([s.x-r*(s.x-o.x),s.y-r*(s.y-o.y)])},getBounds:function(){return this._bounds},addLatLng:function(t,i){return i=i||this._defaultShape(),t=C(t),i.push(t),this._bounds.extend(t),this.redraw()},_setLatLngs:function(t){this._bounds=new T,this._latlngs=this._convertLatLngs(t)},_defaultShape:function(){return jt(this._latlngs)?this._latlngs:this._latlngs[0]},_convertLatLngs:function(t){for(var i=[],e=jt(t),n=0,o=t.length;n=2&&i[0]instanceof M&&i[0].equals(i[e-1])&&i.pop(),i},_setLatLngs:function(t){nn.prototype._setLatLngs.call(this,t),jt(this._latlngs)&&(this._latlngs=[this._latlngs])},_defaultShape:function(){return jt(this._latlngs[0])?this._latlngs[0]:this._latlngs[0][0]},_clipPoints:function(){var t=this._renderer._bounds,i=this.options.weight,e=new x(i,i);if(t=new P(t.min.subtract(e),t.max.add(e)),this._parts=[],this._pxBounds&&this._pxBounds.intersects(t))if(this.options.noClip)this._parts=this._rings;else for(var n,o=0,s=this._rings.length;ot.y!=n.y>t.y&&t.x<(n.x-e.x)*(t.y-e.y)/(n.y-e.y)+e.x&&(u=!u);return u||nn.prototype._containsPoint.call(this,t,!0)}}),sn=Ke.extend({initialize:function(t,i){l(this,i),this._layers={},t&&this.addData(t)},addData:function(t){var i,e,n,o=oi(t)?t:t.features;if(o){for(i=0,e=o.length;i0?o:[i.src]}else{oi(this._url)||(this._url=[this._url]),i.autoplay=!!this.options.autoplay,i.loop=!!this.options.loop;for(var a=0;ao?(i.height=o+\"px\",Q(t,\"leaflet-popup-scrolled\")):tt(t,\"leaflet-popup-scrolled\"),this._containerWidth=this._container.offsetWidth},_animateZoom:function(t){var i=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center),e=this._getAnchor();at(this._container,i.add(e))},_adjustPan:function(){if(this.options.autoPan){this._map._panAnim&&this._map._panAnim.stop();var t=this._map,i=parseInt(q(this._container,\"marginBottom\"),10)||0,e=this._container.offsetHeight+i,n=this._containerWidth,o=new x(this._containerLeft,-e-this._containerBottom);o._add(ht(this._container));var s=t.layerPointToContainerPoint(o),r=w(this.options.autoPanPadding),a=w(this.options.autoPanPaddingTopLeft||r),h=w(this.options.autoPanPaddingBottomRight||r),u=t.getSize(),l=0,c=0;s.x+n+h.x>u.x&&(l=s.x+n-u.x+h.x),s.x-l-a.x<0&&(l=s.x-a.x),s.y+e+h.y>u.y&&(c=s.y+e-u.y+h.y),s.y-c-a.y<0&&(c=s.y-a.y),(l||c)&&t.fire(\"autopanstart\").panBy([l,c])}},_onCloseButtonClick:function(t){this._close(),Lt(t)},_getAnchor:function(){return w(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}});be.mergeOptions({closePopupOnClick:!0}),be.include({openPopup:function(t,i,e){return t instanceof cn||(t=new cn(e).setContent(t)),i&&t.setLatLng(i),this.hasLayer(t)?this:(this._popup&&this._popup.options.autoClose&&this.closePopup(),this._popup=t,this.addLayer(t))},closePopup:function(t){return t&&t!==this._popup||(t=this._popup,this._popup=null),t&&this.removeLayer(t),this}}),qe.include({bindPopup:function(t,i){return t instanceof cn?(l(t,i),this._popup=t,t._source=this):(this._popup&&!i||(this._popup=new cn(i,this)),this._popup.setContent(t)),this._popupHandlersAdded||(this.on({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t,i){if(t instanceof qe||(i=t,t=this),t instanceof Ke)for(var e in this._layers){t=this._layers[e];break}return i||(i=t.getCenter?t.getCenter():t.getLatLng()),this._popup&&this._map&&(this._popup._source=t,this._popup.update(),this._map.openPopup(this._popup,i)),this},closePopup:function(){return this._popup&&this._popup._close(),this},togglePopup:function(t){return this._popup&&(this._popup._map?this.closePopup():this.openPopup(t)),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var i=t.layer||t.target;this._popup&&this._map&&(Lt(t),i instanceof Qe?this.openPopup(t.layer||t.target,t.latlng):this._map.hasLayer(this._popup)&&this._popup._source===i?this.closePopup():this.openPopup(i,t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)},_onKeyPress:function(t){13===t.originalEvent.keyCode&&this._openPopup(t)}});var _n=ln.extend({options:{pane:\"tooltipPane\",offset:[0,0],direction:\"auto\",permanent:!1,sticky:!1,interactive:!1,opacity:.9},onAdd:function(t){ln.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire(\"tooltipopen\",{tooltip:this}),this._source&&this._source.fire(\"tooltipopen\",{tooltip:this},!0)},onRemove:function(t){ln.prototype.onRemove.call(this,t),t.fire(\"tooltipclose\",{tooltip:this}),this._source&&this._source.fire(\"tooltipclose\",{tooltip:this},!0)},getEvents:function(){var t=ln.prototype.getEvents.call(this);return qi&&!this.options.permanent&&(t.preclick=this._close),t},_close:function(){this._map&&this._map.closeTooltip(this)},_initLayout:function(){var t=\"leaflet-tooltip \"+(this.options.className||\"\")+\" leaflet-zoom-\"+(this._zoomAnimated?\"animated\":\"hide\");this._contentNode=this._container=G(\"div\",t)},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var i=this._map,e=this._container,n=i.latLngToContainerPoint(i.getCenter()),o=i.layerPointToContainerPoint(t),s=this.options.direction,r=e.offsetWidth,a=e.offsetHeight,h=w(this.options.offset),u=this._getAnchor();\"top\"===s?t=t.add(w(-r/2+h.x,-a+h.y+u.y,!0)):\"bottom\"===s?t=t.subtract(w(r/2-h.x,-h.y,!0)):\"center\"===s?t=t.subtract(w(r/2+h.x,a/2-u.y+h.y,!0)):\"right\"===s||\"auto\"===s&&o.xthis.options.maxZoom||en&&this._retainParent(o,s,r,n))},_retainChildren:function(t,i,e,n){for(var o=2*t;o<2*t+2;o++)for(var s=2*i;s<2*i+2;s++){var r=new x(o,s);r.z=e+1;var a=this._tileCoordsToKey(r),h=this._tiles[a];h&&h.active?h.retain=!0:(h&&h.loaded&&(h.retain=!0),e+1this.options.maxZoom||void 0!==this.options.minZoom&&o1)this._setView(t,e);else{for(var c=o.min.y;c<=o.max.y;c++)for(var _=o.min.x;_<=o.max.x;_++){var d=new x(_,c);if(d.z=this._tileZoom,this._isValidTile(d)){var p=this._tiles[this._tileCoordsToKey(d)];p?p.current=!0:r.push(d)}}if(r.sort(function(t,i){return t.distanceTo(s)-i.distanceTo(s)}),0!==r.length){this._loading||(this._loading=!0,this.fire(\"loading\"));var m=document.createDocumentFragment();for(_=0;_e.max.x)||!i.wrapLat&&(t.ye.max.y))return!1}if(!this.options.bounds)return!0;var n=this._tileCoordsToBounds(t);return z(this.options.bounds).overlaps(n)},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToNwSe:function(t){var i=this._map,e=this.getTileSize(),n=t.scaleBy(e),o=n.add(e);return[i.unproject(n,t.z),i.unproject(o,t.z)]},_tileCoordsToBounds:function(t){var i=this._tileCoordsToNwSe(t),e=new T(i[0],i[1]);return this.options.noWrap||(e=this._map.wrapLatLngBounds(e)),e},_tileCoordsToKey:function(t){return t.x+\":\"+t.y+\":\"+t.z},_keyToTileCoords:function(t){var i=t.split(\":\"),e=new x(+i[0],+i[1]);return e.z=+i[2],e},_removeTile:function(t){var i=this._tiles[t];i&&(K(i.el),delete this._tiles[t],this.fire(\"tileunload\",{tile:i.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){Q(t,\"leaflet-tile\");var i=this.getTileSize();t.style.width=i.x+\"px\",t.style.height=i.y+\"px\",t.onselectstart=r,t.onmousemove=r,Li&&this.options.opacity<1&&nt(t,this.options.opacity),zi&&!Mi&&(t.style.WebkitBackfaceVisibility=\"hidden\")},_addTile:function(t,i){var n=this._getTilePos(t),o=this._tileCoordsToKey(t),s=this.createTile(this._wrapCoords(t),e(this._tileReady,this,t));this._initTile(s),this.createTile.length<2&&f(e(this._tileReady,this,t,null,s)),at(s,n),this._tiles[o]={el:s,coords:t,current:!0},i.appendChild(s),this.fire(\"tileloadstart\",{tile:s,coords:t})},_tileReady:function(t,i,n){i&&this.fire(\"tileerror\",{error:i,tile:n,coords:t});var o=this._tileCoordsToKey(t);(n=this._tiles[o])&&(n.loaded=+new Date,this._map._fadeAnimated?(nt(n.el,0),g(this._fadeFrame),this._fadeFrame=f(this._updateOpacity,this)):(n.active=!0,this._pruneTiles()),i||(Q(n.el,\"leaflet-tile-loaded\"),this.fire(\"tileload\",{tile:n.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire(\"load\"),Li||!this._map._fadeAnimated?f(this._pruneTiles,this):setTimeout(e(this._pruneTiles,this),250)))},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var i=new x(this._wrapX?s(t.x,this._wrapX):t.x,this._wrapY?s(t.y,this._wrapY):t.y);return i.z=t.z,i},_pxBoundsToTileRange:function(t){var i=this.getTileSize();return new P(t.min.unscaleBy(i).floor(),t.max.unscaleBy(i).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}}),mn=pn.extend({options:{minZoom:0,maxZoom:18,subdomains:\"abc\",errorTileUrl:\"\",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1},initialize:function(t,i){this._url=t,(i=l(this,i)).detectRetina&&Yi&&i.maxZoom>0&&(i.tileSize=Math.floor(i.tileSize/2),i.zoomReverse?(i.zoomOffset--,i.minZoom++):(i.zoomOffset++,i.maxZoom--),i.minZoom=Math.max(0,i.minZoom)),\"string\"==typeof i.subdomains&&(i.subdomains=i.subdomains.split(\"\")),zi||this.on(\"tileunload\",this._onTileRemove)},setUrl:function(t,i){return this._url===t&&void 0===i&&(i=!0),this._url=t,i||this.redraw(),this},createTile:function(t,i){var n=document.createElement(\"img\");return mt(n,\"load\",e(this._tileOnLoad,this,i,n)),mt(n,\"error\",e(this._tileOnError,this,i,n)),(this.options.crossOrigin||\"\"===this.options.crossOrigin)&&(n.crossOrigin=!0===this.options.crossOrigin?\"\":this.options.crossOrigin),n.alt=\"\",n.setAttribute(\"role\",\"presentation\"),n.src=this.getTileUrl(t),n},getTileUrl:function(t){var e={r:Yi?\"@2x\":\"\",s:this._getSubdomain(t),x:t.x,y:t.y,z:this._getZoomForUrl()};if(this._map&&!this._map.options.crs.infinite){var n=this._globalTileRange.max.y-t.y;this.options.tms&&(e.y=n),e[\"-y\"]=n}return _(this._url,i(e,this.options))},_tileOnLoad:function(t,i){Li?setTimeout(e(t,this,null,i),0):t(null,i)},_tileOnError:function(t,i,e){var n=this.options.errorTileUrl;n&&i.getAttribute(\"src\")!==n&&(i.src=n),t(e,i)},_onTileRemove:function(t){t.tile.onload=null},_getZoomForUrl:function(){var t=this._tileZoom,i=this.options.maxZoom,e=this.options.zoomReverse,n=this.options.zoomOffset;return e&&(t=i-t),t+n},_getSubdomain:function(t){var i=Math.abs(t.x+t.y)%this.options.subdomains.length;return this.options.subdomains[i]},_abortLoading:function(){var t,i;for(t in this._tiles)this._tiles[t].coords.z!==this._tileZoom&&((i=this._tiles[t].el).onload=r,i.onerror=r,i.complete||(i.src=si,K(i),delete this._tiles[t]))},_removeTile:function(t){var i=this._tiles[t];if(i)return Si||i.el.setAttribute(\"src\",si),pn.prototype._removeTile.call(this,t)},_tileReady:function(t,i,e){if(this._map&&(!e||e.getAttribute(\"src\")!==si))return pn.prototype._tileReady.call(this,t,i,e)}}),fn=mn.extend({defaultWmsParams:{service:\"WMS\",request:\"GetMap\",layers:\"\",styles:\"\",format:\"image/jpeg\",transparent:!1,version:\"1.1.1\"},options:{crs:null,uppercase:!1},initialize:function(t,e){this._url=t;var n=i({},this.defaultWmsParams);for(var o in e)o in this.options||(n[o]=e[o]);var s=(e=l(this,e)).detectRetina&&Yi?2:1,r=this.getTileSize();n.width=r.x*s,n.height=r.y*s,this.wmsParams=n},onAdd:function(t){this._crs=this.options.crs||t.options.crs,this._wmsVersion=parseFloat(this.wmsParams.version);var i=this._wmsVersion>=1.3?\"crs\":\"srs\";this.wmsParams[i]=this._crs.code,mn.prototype.onAdd.call(this,t)},getTileUrl:function(t){var i=this._tileCoordsToNwSe(t),e=this._crs,n=b(e.project(i[0]),e.project(i[1])),o=n.min,s=n.max,r=(this._wmsVersion>=1.3&&this._crs===Ue?[o.y,o.x,s.y,s.x]:[o.x,o.y,s.x,s.y]).join(\",\"),a=mn.prototype.getTileUrl.call(this,t);return a+c(this.wmsParams,a,this.options.uppercase)+(this.options.uppercase?\"&BBOX=\":\"&bbox=\")+r},setParams:function(t,e){return i(this.wmsParams,t),e||this.redraw(),this}});mn.WMS=fn,Jt.wms=function(t,i){return new fn(t,i)};var gn=qe.extend({options:{padding:.1,tolerance:0},initialize:function(t){l(this,t),n(this),this._layers=this._layers||{}},onAdd:function(){this._container||(this._initContainer(),this._zoomAnimated&&Q(this._container,\"leaflet-zoom-animated\")),this.getPane().appendChild(this._container),this._update(),this.on(\"update\",this._updatePaths,this)},onRemove:function(){this.off(\"update\",this._updatePaths,this),this._destroyContainer()},getEvents:function(){var t={viewreset:this._reset,zoom:this._onZoom,moveend:this._update,zoomend:this._onZoomEnd};return this._zoomAnimated&&(t.zoomanim=this._onAnimZoom),t},_onAnimZoom:function(t){this._updateTransform(t.center,t.zoom)},_onZoom:function(){this._updateTransform(this._map.getCenter(),this._map.getZoom())},_updateTransform:function(t,i){var e=this._map.getZoomScale(i,this._zoom),n=ht(this._container),o=this._map.getSize().multiplyBy(.5+this.options.padding),s=this._map.project(this._center,i),r=this._map.project(t,i).subtract(s),a=o.multiplyBy(-e).add(n).add(o).subtract(r);ji?rt(this._container,a,e):at(this._container,a)},_reset:function(){this._update(),this._updateTransform(this._center,this._zoom);for(var t in this._layers)this._layers[t]._reset()},_onZoomEnd:function(){for(var t in this._layers)this._layers[t]._project()},_updatePaths:function(){for(var t in this._layers)this._layers[t]._update()},_update:function(){var t=this.options.padding,i=this._map.getSize(),e=this._map.containerPointToLayerPoint(i.multiplyBy(-t)).round();this._bounds=new P(e,e.add(i.multiplyBy(1+2*t)).round()),this._center=this._map.getCenter(),this._zoom=this._map.getZoom()}}),vn=gn.extend({getEvents:function(){var t=gn.prototype.getEvents.call(this);return t.viewprereset=this._onViewPreReset,t},_onViewPreReset:function(){this._postponeUpdatePaths=!0},onAdd:function(){gn.prototype.onAdd.call(this),this._draw()},_initContainer:function(){var t=this._container=document.createElement(\"canvas\");mt(t,\"mousemove\",o(this._onMouseMove,32,this),this),mt(t,\"click dblclick mousedown mouseup contextmenu\",this._onClick,this),mt(t,\"mouseout\",this._handleMouseOut,this),this._ctx=t.getContext(\"2d\")},_destroyContainer:function(){g(this._redrawRequest),delete this._ctx,K(this._container),ft(this._container),delete this._container},_updatePaths:function(){if(!this._postponeUpdatePaths){this._redrawBounds=null;for(var t in this._layers)this._layers[t]._update();this._redraw()}},_update:function(){if(!this._map._animatingZoom||!this._bounds){gn.prototype._update.call(this);var t=this._bounds,i=this._container,e=t.getSize(),n=Yi?2:1;at(i,t.min),i.width=n*e.x,i.height=n*e.y,i.style.width=e.x+\"px\",i.style.height=e.y+\"px\",Yi&&this._ctx.scale(2,2),this._ctx.translate(-t.min.x,-t.min.y),this.fire(\"update\")}},_reset:function(){gn.prototype._reset.call(this),this._postponeUpdatePaths&&(this._postponeUpdatePaths=!1,this._updatePaths())},_initPath:function(t){this._updateDashArray(t),this._layers[n(t)]=t;var i=t._order={layer:t,prev:this._drawLast,next:null};this._drawLast&&(this._drawLast.next=i),this._drawLast=i,this._drawFirst=this._drawFirst||this._drawLast},_addPath:function(t){this._requestRedraw(t)},_removePath:function(t){var i=t._order,e=i.next,o=i.prev;e?e.prev=o:this._drawLast=o,o?o.next=e:this._drawFirst=e,delete t._order,delete this._layers[n(t)],this._requestRedraw(t)},_updatePath:function(t){this._extendRedrawBounds(t),t._project(),t._update(),this._requestRedraw(t)},_updateStyle:function(t){this._updateDashArray(t),this._requestRedraw(t)},_updateDashArray:function(t){if(\"string\"==typeof t.options.dashArray){var i,e,n=t.options.dashArray.split(/[, ]+/),o=[];for(e=0;e')}}catch(t){return function(t){return document.createElement(\"<\"+t+' xmlns=\"urn:schemas-microsoft.com:vml\" class=\"lvml\">')}}}(),xn={_initContainer:function(){this._container=G(\"div\",\"leaflet-vml-container\")},_update:function(){this._map._animatingZoom||(gn.prototype._update.call(this),this.fire(\"update\"))},_initPath:function(t){var i=t._container=yn(\"shape\");Q(i,\"leaflet-vml-shape \"+(this.options.className||\"\")),i.coordsize=\"1 1\",t._path=yn(\"path\"),i.appendChild(t._path),this._updateStyle(t),this._layers[n(t)]=t},_addPath:function(t){var i=t._container;this._container.appendChild(i),t.options.interactive&&t.addInteractiveTarget(i)},_removePath:function(t){var i=t._container;K(i),t.removeInteractiveTarget(i),delete this._layers[n(t)]},_updateStyle:function(t){var i=t._stroke,e=t._fill,n=t.options,o=t._container;o.stroked=!!n.stroke,o.filled=!!n.fill,n.stroke?(i||(i=t._stroke=yn(\"stroke\")),o.appendChild(i),i.weight=n.weight+\"px\",i.color=n.color,i.opacity=n.opacity,n.dashArray?i.dashStyle=oi(n.dashArray)?n.dashArray.join(\" \"):n.dashArray.replace(/( *, *)/g,\" \"):i.dashStyle=\"\",i.endcap=n.lineCap.replace(\"butt\",\"flat\"),i.joinstyle=n.lineJoin):i&&(o.removeChild(i),t._stroke=null),n.fill?(e||(e=t._fill=yn(\"fill\")),o.appendChild(e),e.color=n.fillColor||n.color,e.opacity=n.fillOpacity):e&&(o.removeChild(e),t._fill=null)},_updateCircle:function(t){var i=t._point.round(),e=Math.round(t._radius),n=Math.round(t._radiusY||e);this._setPath(t,t._empty()?\"M0 0\":\"AL \"+i.x+\",\"+i.y+\" \"+e+\",\"+n+\" 0,23592600\")},_setPath:function(t,i){t._path.v=i},_bringToFront:function(t){X(t._container)},_bringToBack:function(t){J(t._container)}},wn=$i?yn:E,Pn=gn.extend({getEvents:function(){var t=gn.prototype.getEvents.call(this);return t.zoomstart=this._onZoomStart,t},_initContainer:function(){this._container=wn(\"svg\"),this._container.setAttribute(\"pointer-events\",\"none\"),this._rootGroup=wn(\"g\"),this._container.appendChild(this._rootGroup)},_destroyContainer:function(){K(this._container),ft(this._container),delete this._container,delete this._rootGroup,delete this._svgSize},_onZoomStart:function(){this._update()},_update:function(){if(!this._map._animatingZoom||!this._bounds){gn.prototype._update.call(this);var t=this._bounds,i=t.getSize(),e=this._container;this._svgSize&&this._svgSize.equals(i)||(this._svgSize=i,e.setAttribute(\"width\",i.x),e.setAttribute(\"height\",i.y)),at(e,t.min),e.setAttribute(\"viewBox\",[t.min.x,t.min.y,i.x,i.y].join(\" \")),this.fire(\"update\")}},_initPath:function(t){var i=t._path=wn(\"path\");t.options.className&&Q(i,t.options.className),t.options.interactive&&Q(i,\"leaflet-interactive\"),this._updateStyle(t),this._layers[n(t)]=t},_addPath:function(t){this._rootGroup||this._initContainer(),this._rootGroup.appendChild(t._path),t.addInteractiveTarget(t._path)},_removePath:function(t){K(t._path),t.removeInteractiveTarget(t._path),delete this._layers[n(t)]},_updatePath:function(t){t._project(),t._update()},_updateStyle:function(t){var i=t._path,e=t.options;i&&(e.stroke?(i.setAttribute(\"stroke\",e.color),i.setAttribute(\"stroke-opacity\",e.opacity),i.setAttribute(\"stroke-width\",e.weight),i.setAttribute(\"stroke-linecap\",e.lineCap),i.setAttribute(\"stroke-linejoin\",e.lineJoin),e.dashArray?i.setAttribute(\"stroke-dasharray\",e.dashArray):i.removeAttribute(\"stroke-dasharray\"),e.dashOffset?i.setAttribute(\"stroke-dashoffset\",e.dashOffset):i.removeAttribute(\"stroke-dashoffset\")):i.setAttribute(\"stroke\",\"none\"),e.fill?(i.setAttribute(\"fill\",e.fillColor||e.color),i.setAttribute(\"fill-opacity\",e.fillOpacity),i.setAttribute(\"fill-rule\",e.fillRule||\"evenodd\")):i.setAttribute(\"fill\",\"none\"))},_updatePoly:function(t,i){this._setPath(t,k(t._parts,i))},_updateCircle:function(t){var i=t._point,e=Math.max(Math.round(t._radius),1),n=\"a\"+e+\",\"+(Math.max(Math.round(t._radiusY),1)||e)+\" 0 1,0 \",o=t._empty()?\"M0 0\":\"M\"+(i.x-e)+\",\"+i.y+n+2*e+\",0 \"+n+2*-e+\",0 \";this._setPath(t,o)},_setPath:function(t,i){t._path.setAttribute(\"d\",i)},_bringToFront:function(t){X(t._path)},_bringToBack:function(t){J(t._path)}});$i&&Pn.include(xn),be.include({getRenderer:function(t){var i=t.options.renderer||this._getPaneRenderer(t.options.pane)||this.options.renderer||this._renderer;return i||(i=this._renderer=this._createRenderer()),this.hasLayer(i)||this.addLayer(i),i},_getPaneRenderer:function(t){if(\"overlayPane\"===t||void 0===t)return!1;var i=this._paneRenderers[t];return void 0===i&&(i=this._createRenderer({pane:t}),this._paneRenderers[t]=i),i},_createRenderer:function(t){return this.options.preferCanvas&&$t(t)||Qt(t)}});var Ln=on.extend({initialize:function(t,i){on.prototype.initialize.call(this,this._boundsToLatLngs(t),i)},setBounds:function(t){return this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return t=z(t),[t.getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}});Pn.create=wn,Pn.pointsToPath=k,sn.geometryToLayer=Ft,sn.coordsToLatLng=Ut,sn.coordsToLatLngs=Vt,sn.latLngToCoords=qt,sn.latLngsToCoords=Gt,sn.getFeature=Kt,sn.asFeature=Yt,be.mergeOptions({boxZoom:!0});var bn=Ee.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._resetStateTimeout=0,t.on(\"unload\",this._destroy,this)},addHooks:function(){mt(this._container,\"mousedown\",this._onMouseDown,this)},removeHooks:function(){ft(this._container,\"mousedown\",this._onMouseDown,this)},moved:function(){return this._moved},_destroy:function(){K(this._pane),delete this._pane},_resetState:function(){this._resetStateTimeout=0,this._moved=!1},_clearDeferredResetState:function(){0!==this._resetStateTimeout&&(clearTimeout(this._resetStateTimeout),this._resetStateTimeout=0)},_onMouseDown:function(t){if(!t.shiftKey||1!==t.which&&1!==t.button)return!1;this._clearDeferredResetState(),this._resetState(),fi(),ut(),this._startPoint=this._map.mouseEventToContainerPoint(t),mt(document,{contextmenu:Lt,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=G(\"div\",\"leaflet-zoom-box\",this._container),Q(this._container,\"leaflet-crosshair\"),this._map.fire(\"boxzoomstart\")),this._point=this._map.mouseEventToContainerPoint(t);var i=new P(this._point,this._startPoint),e=i.getSize();at(this._box,i.min),this._box.style.width=e.x+\"px\",this._box.style.height=e.y+\"px\"},_finish:function(){this._moved&&(K(this._box),tt(this._container,\"leaflet-crosshair\")),gi(),lt(),ft(document,{contextmenu:Lt,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){if((1===t.which||1===t.button)&&(this._finish(),this._moved)){this._clearDeferredResetState(),this._resetStateTimeout=setTimeout(e(this._resetState,this),0);var i=new T(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point));this._map.fitBounds(i).fire(\"boxzoomend\",{boxZoomBounds:i})}},_onKeyDown:function(t){27===t.keyCode&&this._finish()}});be.addInitHook(\"addHandler\",\"boxZoom\",bn),be.mergeOptions({doubleClickZoom:!0});var Tn=Ee.extend({addHooks:function(){this._map.on(\"dblclick\",this._onDoubleClick,this)},removeHooks:function(){this._map.off(\"dblclick\",this._onDoubleClick,this)},_onDoubleClick:function(t){var i=this._map,e=i.getZoom(),n=i.options.zoomDelta,o=t.originalEvent.shiftKey?e-n:e+n;\"center\"===i.options.doubleClickZoom?i.setZoom(o):i.setZoomAround(t.containerPoint,o)}});be.addInitHook(\"addHandler\",\"doubleClickZoom\",Tn),be.mergeOptions({dragging:!0,inertia:!Mi,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,easeLinearity:.2,worldCopyJump:!1,maxBoundsViscosity:0});var zn=Ee.extend({addHooks:function(){if(!this._draggable){var t=this._map;this._draggable=new Re(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),this._draggable.on(\"predrag\",this._onPreDragLimit,this),t.options.worldCopyJump&&(this._draggable.on(\"predrag\",this._onPreDragWrap,this),t.on(\"zoomend\",this._onZoomEnd,this),t.whenReady(this._onZoomEnd,this))}Q(this._map._container,\"leaflet-grab leaflet-touch-drag\"),this._draggable.enable(),this._positions=[],this._times=[]},removeHooks:function(){tt(this._map._container,\"leaflet-grab\"),tt(this._map._container,\"leaflet-touch-drag\"),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},moving:function(){return this._draggable&&this._draggable._moving},_onDragStart:function(){var t=this._map;if(t._stop(),this._map.options.maxBounds&&this._map.options.maxBoundsViscosity){var i=z(this._map.options.maxBounds);this._offsetLimit=b(this._map.latLngToContainerPoint(i.getNorthWest()).multiplyBy(-1),this._map.latLngToContainerPoint(i.getSouthEast()).multiplyBy(-1).add(this._map.getSize())),this._viscosity=Math.min(1,Math.max(0,this._map.options.maxBoundsViscosity))}else this._offsetLimit=null;t.fire(\"movestart\").fire(\"dragstart\"),t.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(t){if(this._map.options.inertia){var i=this._lastTime=+new Date,e=this._lastPos=this._draggable._absPos||this._draggable._newPos;this._positions.push(e),this._times.push(i),this._prunePositions(i)}this._map.fire(\"move\",t).fire(\"drag\",t)},_prunePositions:function(t){for(;this._positions.length>1&&t-this._times[0]>50;)this._positions.shift(),this._times.shift()},_onZoomEnd:function(){var t=this._map.getSize().divideBy(2),i=this._map.latLngToLayerPoint([0,0]);this._initialWorldOffset=i.subtract(t).x,this._worldWidth=this._map.getPixelWorldBounds().getSize().x},_viscousLimit:function(t,i){return t-(t-i)*this._viscosity},_onPreDragLimit:function(){if(this._viscosity&&this._offsetLimit){var t=this._draggable._newPos.subtract(this._draggable._startPos),i=this._offsetLimit;t.xi.max.x&&(t.x=this._viscousLimit(t.x,i.max.x)),t.y>i.max.y&&(t.y=this._viscousLimit(t.y,i.max.y)),this._draggable._newPos=this._draggable._startPos.add(t)}},_onPreDragWrap:function(){var t=this._worldWidth,i=Math.round(t/2),e=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-i+e)%t+i-e,s=(n+i+e)%t-i-e,r=Math.abs(o+e)0?s:-s))-i;this._delta=0,this._startTime=null,r&&(\"center\"===t.options.scrollWheelZoom?t.setZoom(i+r):t.setZoomAround(this._lastMousePos,i+r))}});be.addInitHook(\"addHandler\",\"scrollWheelZoom\",Cn),be.mergeOptions({tap:!0,tapTolerance:15});var Sn=Ee.extend({addHooks:function(){mt(this._map._container,\"touchstart\",this._onDown,this)},removeHooks:function(){ft(this._map._container,\"touchstart\",this._onDown,this)},_onDown:function(t){if(t.touches){if(Pt(t),this._fireClick=!0,t.touches.length>1)return this._fireClick=!1,void clearTimeout(this._holdTimeout);var i=t.touches[0],n=i.target;this._startPos=this._newPos=new x(i.clientX,i.clientY),n.tagName&&\"a\"===n.tagName.toLowerCase()&&Q(n,\"leaflet-active\"),this._holdTimeout=setTimeout(e(function(){this._isTapValid()&&(this._fireClick=!1,this._onUp(),this._simulateEvent(\"contextmenu\",i))},this),1e3),this._simulateEvent(\"mousedown\",i),mt(document,{touchmove:this._onMove,touchend:this._onUp},this)}},_onUp:function(t){if(clearTimeout(this._holdTimeout),ft(document,{touchmove:this._onMove,touchend:this._onUp},this),this._fireClick&&t&&t.changedTouches){var i=t.changedTouches[0],e=i.target;e&&e.tagName&&\"a\"===e.tagName.toLowerCase()&&tt(e,\"leaflet-active\"),this._simulateEvent(\"mouseup\",i),this._isTapValid()&&this._simulateEvent(\"click\",i)}},_isTapValid:function(){return this._newPos.distanceTo(this._startPos)<=this._map.options.tapTolerance},_onMove:function(t){var i=t.touches[0];this._newPos=new x(i.clientX,i.clientY),this._simulateEvent(\"mousemove\",i)},_simulateEvent:function(t,i){var e=document.createEvent(\"MouseEvents\");e._simulated=!0,i.target._simulatedClick=!0,e.initMouseEvent(t,!0,!0,window,1,i.screenX,i.screenY,i.clientX,i.clientY,!1,!1,!1,!1,0,null),i.target.dispatchEvent(e)}});qi&&!Vi&&be.addInitHook(\"addHandler\",\"tap\",Sn),be.mergeOptions({touchZoom:qi&&!Mi,bounceAtZoomLimits:!0});var Zn=Ee.extend({addHooks:function(){Q(this._map._container,\"leaflet-touch-zoom\"),mt(this._map._container,\"touchstart\",this._onTouchStart,this)},removeHooks:function(){tt(this._map._container,\"leaflet-touch-zoom\"),ft(this._map._container,\"touchstart\",this._onTouchStart,this)},_onTouchStart:function(t){var i=this._map;if(t.touches&&2===t.touches.length&&!i._animatingZoom&&!this._zooming){var e=i.mouseEventToContainerPoint(t.touches[0]),n=i.mouseEventToContainerPoint(t.touches[1]);this._centerPoint=i.getSize()._divideBy(2),this._startLatLng=i.containerPointToLatLng(this._centerPoint),\"center\"!==i.options.touchZoom&&(this._pinchStartLatLng=i.containerPointToLatLng(e.add(n)._divideBy(2))),this._startDist=e.distanceTo(n),this._startZoom=i.getZoom(),this._moved=!1,this._zooming=!0,i._stop(),mt(document,\"touchmove\",this._onTouchMove,this),mt(document,\"touchend\",this._onTouchEnd,this),Pt(t)}},_onTouchMove:function(t){if(t.touches&&2===t.touches.length&&this._zooming){var i=this._map,n=i.mouseEventToContainerPoint(t.touches[0]),o=i.mouseEventToContainerPoint(t.touches[1]),s=n.distanceTo(o)/this._startDist;if(this._zoom=i.getScaleZoom(s,this._startZoom),!i.options.bounceAtZoomLimits&&(this._zoomi.getMaxZoom()&&s>1)&&(this._zoom=i._limitZoom(this._zoom)),\"center\"===i.options.touchZoom){if(this._center=this._startLatLng,1===s)return}else{var r=n._add(o)._divideBy(2)._subtract(this._centerPoint);if(1===s&&0===r.x&&0===r.y)return;this._center=i.unproject(i.project(this._pinchStartLatLng,this._zoom).subtract(r),this._zoom)}this._moved||(i._moveStart(!0,!1),this._moved=!0),g(this._animRequest);var a=e(i._move,i,this._center,this._zoom,{pinch:!0,round:!1});this._animRequest=f(a,this,!0),Pt(t)}},_onTouchEnd:function(){this._moved&&this._zooming?(this._zooming=!1,g(this._animRequest),ft(document,\"touchmove\",this._onTouchMove),ft(document,\"touchend\",this._onTouchEnd),this._map.options.zoomAnimation?this._map._animateZoom(this._center,this._map._limitZoom(this._zoom),!0,this._map.options.zoomSnap):this._map._resetView(this._center,this._map._limitZoom(this._zoom))):this._zooming=!1}});be.addInitHook(\"addHandler\",\"touchZoom\",Zn),be.BoxZoom=bn,be.DoubleClickZoom=Tn,be.Drag=zn,be.Keyboard=Mn,be.ScrollWheelZoom=Cn,be.Tap=Sn,be.TouchZoom=Zn,Object.freeze=ti,t.version=\"1.4.0+HEAD.3337f36\",t.Control=Te,t.control=ze,t.Browser=Qi,t.Evented=ci,t.Mixin=Be,t.Util=ui,t.Class=v,t.Handler=Ee,t.extend=i,t.bind=e,t.stamp=n,t.setOptions=l,t.DomEvent=Pe,t.DomUtil=ve,t.PosAnimation=Le,t.Draggable=Re,t.LineUtil=Ne,t.PolyUtil=De,t.Point=x,t.point=w,t.Bounds=P,t.bounds=b,t.Transformation=S,t.transformation=Z,t.Projection=He,t.LatLng=M,t.latLng=C,t.LatLngBounds=T,t.latLngBounds=z,t.CRS=di,t.GeoJSON=sn,t.geoJSON=Xt,t.geoJson=an,t.Layer=qe,t.LayerGroup=Ge,t.layerGroup=function(t,i){return new Ge(t,i)},t.FeatureGroup=Ke,t.featureGroup=function(t){return new Ke(t)},t.ImageOverlay=hn,t.imageOverlay=function(t,i,e){return new hn(t,i,e)},t.VideoOverlay=un,t.videoOverlay=function(t,i,e){return new un(t,i,e)},t.DivOverlay=ln,t.Popup=cn,t.popup=function(t,i){return new cn(t,i)},t.Tooltip=_n,t.tooltip=function(t,i){return new _n(t,i)},t.Icon=Ye,t.icon=function(t){return new Ye(t)},t.DivIcon=dn,t.divIcon=function(t){return new dn(t)},t.Marker=$e,t.marker=function(t,i){return new $e(t,i)},t.TileLayer=mn,t.tileLayer=Jt,t.GridLayer=pn,t.gridLayer=function(t){return new pn(t)},t.SVG=Pn,t.svg=Qt,t.Renderer=gn,t.Canvas=vn,t.canvas=$t,t.Path=Qe,t.CircleMarker=tn,t.circleMarker=function(t,i){return new tn(t,i)},t.Circle=en,t.circle=function(t,i,e){return new en(t,i,e)},t.Polyline=nn,t.polyline=function(t,i){return new nn(t,i)},t.Polygon=on,t.polygon=function(t,i){return new on(t,i)},t.Rectangle=Ln,t.rectangle=function(t,i){return new Ln(t,i)},t.Map=be,t.map=function(t,i){return new be(t,i)};var En=window.L;t.noConflict=function(){return window.L=En,this},window.L=t});", "/** @odoo-module */\n\nexport function mockJoinSpreadsheetSession(resModel) {\n return function (route, args) {\n const [id] = args.args;\n const record = this.models[resModel].records.find((record) => record.id === id);\n if (!record) {\n throw new Error(`Spreadsheet ${id} does not exist`);\n }\n return {\n raw: JSON.parse(record.raw),\n name: record.name,\n is_favorited: record.is_favorited,\n revisions: [],\n isReadonly: false,\n };\n };\n}\n", "odoo.define('web_gantt.gantt_mobile_tests', function (require) {\n\"use strict\";\n\nconst GanttView = require('web_gantt.GanttView');\nconst testUtils = require('web.test_utils');\n\nconst createView = testUtils.createView;\n\nlet initialDate = new Date(2018, 11, 20, 8, 0, 0);\ninitialDate = new Date(initialDate.getTime() - initialDate.getTimezoneOffset() * 60 * 1000);\n\nQUnit.module('LegacyViews', {\n beforeEach: function () {\n this.data = {\n tasks: {\n fields: {\n id: {string: 'ID', type: 'integer'},\n start: {string: 'Start Date', type: 'datetime'},\n stop: {string: 'Stop Date', type: 'datetime'},\n user_id: {string: 'Assign To', type: 'many2one', relation: 'users'},\n },\n records: [\n { id: 1, start: '2018-11-30 18:30:00', stop: '2018-12-31 18:29:59', user_id: 1},\n { id: 2, start: '2018-12-17 11:30:00', stop: '2018-12-22 06:29:59', user_id: 2},\n { id: 3, start: '2018-12-27 06:30:00', stop: '2019-01-03 06:29:59', user_id: 2},\n { id: 4, start: '2018-12-19 22:30:00', stop: '2018-12-20 06:29:59', user_id: 1},\n { id: 5, start: '2018-11-08 01:53:10', stop: '2018-12-04 01:34:34', user_id: 1},\n ],\n },\n users: {\n fields: {\n id: {string: 'ID', type: 'integer'},\n name: {string: 'Name', type: 'char'},\n },\n records: [\n {id: 1, name: 'User 1'},\n {id: 2, name: 'User 2'},\n ],\n },\n };\n },\n}, function () {\n QUnit.module('GanttView (legacy) - Mobile');\n\n QUnit.test('Progressbar: check the progressbar percentage visibility.', async function (assert) {\n assert.expect(11);\n\n const gantt = await createView({\n View: GanttView,\n model: 'tasks',\n data: this.data,\n arch: `\n \n `,\n viewOptions: {\n initialDate: initialDate,\n },\n config: {device: {isMobile: true}},\n mockRPC: function (route, args) {\n if (args.method === 'gantt_progress_bar') {\n assert.strictEqual(args.model, \"tasks\");\n assert.deepEqual(args.args[0], ['user_id']);\n assert.deepEqual(args.args[1], {user_id: [1, 2]});\n return Promise.resolve({\n user_id: {\n 1: {value: 50, max_value: 100},\n 2: {value: 25, max_value: 200},\n }\n });\n }\n return this._super.apply(this, arguments);\n },\n });\n\n const progressbar = gantt.el.querySelectorAll('.o_gantt_row_sidebar .o_gantt_progressbar');\n assert.strictEqual(progressbar.length, 2, \"Gantt should include two progressbars\");\n assert.strictEqual(progressbar[0].style.width, \"50%\");\n assert.strictEqual(progressbar[1].style.width, \"12.5%\");\n assert.hasClass(progressbar[0], 'o_gantt_group_success', \"Progress bar should have the success class\");\n assert.hasClass(progressbar[1], 'o_gantt_group_success', \"Progress bar should have the success class\");\n const progressbarText = gantt.el.querySelectorAll('.o_gantt_row_sidebar .o_gantt_text_mobile');\n assert.hasClass(progressbarText[0], 'o_gantt_text_mobile', \"Progress bar should be visible.\");\n assert.hasClass(progressbarText[1], 'o_gantt_text_mobile', \"Progress bar should be visible.\");\n assert.isVisible(gantt.el.querySelector('.o_gantt_row_sidebar .o_gantt_group_hours'), \"Progressbar Title should be visible.\");\n\n gantt.destroy();\n });\n QUnit.test('horizontal scroll applies only to the content, not to the whole controller [SMALL SCREEN]', async function (assert) {\n assert.expect(7);\n const gantt = await createView({\n View: GanttView,\n model: 'tasks',\n data: this.data,\n arch: `\n \n `,\n viewOptions: {\n initialDate: initialDate,\n },\n debug: true, // for this test, we need the elements to be visible in the viewport\n });\n\n const o_view_controller = document.querySelector(\".o_view_controller\");\n const o_content = o_view_controller.querySelector('.o_content');\n const o_gantt_header_cell = gantt.el.querySelector('.o_gantt_header_cell:first-child');\n const o_gantt_cp_btn_today = gantt.el.querySelector('.o_gantt_button_today');\n const initialXCpBtn = o_gantt_cp_btn_today.getBoundingClientRect().x;\n const initialXHeaderCell = o_gantt_header_cell.getBoundingClientRect().x;\n\n assert.hasClass(o_view_controller, \"o_action_delegate_scroll\",\n \"the 'o_view_controller' should be have the 'o_action_delegate_scroll'.\");\n assert.strictEqual(window.getComputedStyle(o_view_controller).overflow,\"hidden\",\n \"The view controller should have overflow hidden\");\n assert.strictEqual(window.getComputedStyle(o_content).overflow,\"auto\", \"The view content should have the overflow auto\");\n assert.strictEqual(o_content.scrollLeft, 0, \"Te o_content should not have scroll value\");\n\n // Horizontal scroll\n o_content.scrollLeft = 100;\n await testUtils.nextTick();\n\n assert.strictEqual(o_content.scrollLeft, 100, \"the o_content should be 100 due to the overflow auto\");\n assert.ok(o_gantt_header_cell.getBoundingClientRect().x < initialXHeaderCell,\n \"the gantt header cell x position value should be lower after the scroll\");\n assert.ok(o_gantt_cp_btn_today.getBoundingClientRect().x === initialXCpBtn,\n \"the btn x position of the control panel button should be the same after the scroll\");\n gantt.destroy();\n });\n});\n});\n", "/** @odoo-module **/\n\nimport { patch } from \"@web/core/utils/patch\";\nimport { MockServer } from \"@web/../tests/helpers/mock_server\";\n\npatch(MockServer.prototype, \"google_calendar_mock_server\", {\n /**\n * Simulate the creation of a custom appointment type\n * by receiving a list of slots.\n * @override\n */\n async _performRPC(route, args) {\n if (route === '/google_calendar/sync_data') {\n return Promise.resolve({status: 'no_new_event_from_google'});\n }\n return this._super(...arguments);\n },\n});\n", "/** @odoo-module **/\n\nimport session from 'web.session';\nimport mobile from 'web_mobile.core';\nimport { patchWithCleanup, clickSave, getFixture } from \"@web/../tests/helpers/utils\";\nimport { makeView, setupViewRegistries } from \"@web/../tests/views/helpers\";\n\n\nconst MY_IMAGE = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';\nconst BASE64_PNG_HEADER = \"iVBORw0KGg\";\n\nlet serverData;\nlet target;\n\nQUnit.module(\"hr_mobile\", (hooks) => {\n hooks.beforeEach(async () => {\n target = getFixture();\n serverData = {\n models: {\n users: {\n fields: {\n name: { string: \"name\", type: \"char\" },\n },\n records: [],\n },\n },\n };\n setupViewRegistries();\n });\n\n QUnit.test('EmployeeProfileFormView should call native updateAccount method when saving record', async function (assert) {\n assert.expect(4);\n\n patchWithCleanup(mobile.methods, {\n updateAccount( options ) {\n const { avatar, name, username } = options;\n assert.ok(\"should call updateAccount\");\n assert.ok(avatar.startsWith(BASE64_PNG_HEADER), \"should have a PNG base64 encoded avatar\");\n assert.strictEqual(name, \"Marc Demo\");\n assert.strictEqual(username, \"demo\");\n return Promise.resolve();\n },\n });\n\n patchWithCleanup(session, {\n url(path) {\n if (path === '/web/image') {\n return `data:image/png;base64,${MY_IMAGE}`;\n }\n return this._super(...arguments);\n },\n username: \"demo\",\n name: \"Marc Demo\",\n });\n\n await makeView({\n type: \"form\",\n resModel: 'users',\n serverData: serverData,\n arch: `\n
\n \n \n \n
`,\n });\n\n await clickSave(target);\n });\n});\n", "/** @odoo-module */\n\nimport { click, getFixture } from \"@web/../tests/helpers/utils\";\nimport { createSpreadsheetDashboard } from \"../utils/dashboard_action\";\nimport { getDashboardServerData } from \"../utils/data\";\n\nQUnit.module(\"spreadsheet_dashboard > Mobile Dashboard action\");\n\nQUnit.test(\"is empty with no figures\", async (assert) => {\n await createSpreadsheetDashboard();\n const fixture = getFixture();\n assert.containsOnce(fixture, \".o_mobile_dashboard\");\n const content = fixture.querySelector(\".o_mobile_dashboard\");\n assert.deepEqual(content.innerText.split(\"\\n\"), [\n \"Dashboard CRM 1\",\n \"Only chart figures are displayed in small screens but this dashboard doesn't contain any\",\n ]);\n});\n\nQUnit.test(\"with no available dashboard\", async (assert) => {\n const serverData = getDashboardServerData();\n serverData.models[\"spreadsheet.dashboard\"].records = [];\n serverData.models[\"spreadsheet.dashboard.group\"].records = [];\n await createSpreadsheetDashboard({ serverData });\n const fixture = getFixture();\n const content = fixture.querySelector(\".o_mobile_dashboard\");\n assert.deepEqual(content.innerText, \"No available dashboard\");\n});\n\nQUnit.test(\"displays figures in first sheet\", async (assert) => {\n const figure = {\n tag: \"chart\",\n height: 500,\n width: 500,\n x: 100,\n y: 100,\n data: {\n type: \"line\",\n dataSetsHaveTitle: false,\n dataSets: [\"A1\"],\n legendPosition: \"top\",\n verticalAxisPosition: \"left\",\n title: \"\",\n },\n };\n const spreadsheetData = {\n sheets: [\n {\n id: \"sheet1\",\n figures: [{ ...figure, id: \"figure1\" }],\n },\n {\n id: \"sheet2\",\n figures: [{ ...figure, id: \"figure2\" }],\n },\n ],\n };\n const serverData = getDashboardServerData();\n serverData.models[\"spreadsheet.dashboard.group\"].records = [\n {\n dashboard_ids: [789],\n id: 1,\n name: \"Chart\",\n },\n ];\n serverData.models[\"spreadsheet.dashboard\"].records = [\n {\n id: 789,\n name: \"Spreadsheet with chart figure\",\n json_data: JSON.stringify(spreadsheetData),\n raw: JSON.stringify(spreadsheetData),\n dashboard_group_id: 1,\n },\n ];\n const fixture = getFixture();\n await createSpreadsheetDashboard({ serverData });\n assert.containsOnce(fixture, \".o-chart-container\");\n});\n\nQUnit.test(\"can switch dashboard\", async (assert) => {\n await createSpreadsheetDashboard();\n const fixture = getFixture();\n assert.strictEqual(\n fixture.querySelector(\".o_search_panel_summary\").innerText,\n \"Dashboard CRM 1\"\n );\n await click(fixture, \".o_search_panel_current_selection\");\n const dashboardElements = [...document.querySelectorAll(\"section header.list-group-item\")];\n assert.strictEqual(dashboardElements[0].classList.contains(\"active\"), true);\n assert.deepEqual(\n dashboardElements.map((el) => el.innerText),\n [\"Dashboard CRM 1\", \"Dashboard CRM 2\", \"Dashboard Accounting 1\"]\n );\n await click(dashboardElements[1]);\n assert.strictEqual(\n fixture.querySelector(\".o_search_panel_summary\").innerText,\n \"Dashboard CRM 2\"\n );\n});\n\nQUnit.test(\"can go back from dashboard selection\", async (assert) => {\n await createSpreadsheetDashboard();\n const fixture = getFixture();\n assert.containsOnce(fixture, \".o_mobile_dashboard\");\n assert.strictEqual(\n fixture.querySelector(\".o_search_panel_summary\").innerText,\n \"Dashboard CRM 1\"\n );\n await click(fixture, \".o_search_panel_current_selection\");\n await click(document, \".o_mobile_search_button\");\n assert.strictEqual(\n fixture.querySelector(\".o_search_panel_summary\").innerText,\n \"Dashboard CRM 1\"\n );\n});\n", "/** @odoo-module */\n\nimport { createWebClient, doAction } from \"@web/../tests/webclient/helpers\";\nimport { getDashboardServerData } from \"./data\";\n\n/**\n * @param {object} params\n * @param {object} [params.serverData]\n * @param {function} [params.mockRPC]\n * @param {number} [params.spreadsheetId]\n * @returns {Promise}\n */\nexport async function createSpreadsheetDashboard(params = {}) {\n const webClient = await createWebClient({\n serverData: params.serverData || getDashboardServerData(),\n mockRPC: params.mockRPC,\n });\n return await doAction(webClient, {\n type: \"ir.actions.client\",\n tag: \"action_spreadsheet_dashboard\",\n params: {\n dashboard_id: params.spreadsheetId,\n },\n });\n}\n", "/** @odoo-module */\n\nexport function getDashboardServerData() {\n return {\n models: {\n \"spreadsheet.dashboard\": {\n fields: {\n json_data: { type: \"char\" },\n raw: { type: \"char \" },\n name: { type: \"char\" },\n dashboard_group_id: {\n type: \"many2one\",\n relation: \"spreadsheet.dashboard.group\",\n },\n },\n records: [\n {\n id: 1,\n raw: \"{}\",\n json_data: \"{}\",\n name: \"Dashboard CRM 1\",\n dashboard_group_id: 1,\n },\n {\n id: 2,\n raw: \"{}\",\n json_data: \"{}\",\n name: \"Dashboard CRM 2\",\n dashboard_group_id: 1,\n },\n {\n id: 3,\n raw: \"{}\",\n json_data: \"{}\",\n name: \"Dashboard Accounting 1\",\n dashboard_group_id: 2,\n },\n ],\n },\n \"spreadsheet.dashboard.group\": {\n fields: {\n name: { type: \"char\" },\n dashboard_ids: {\n type: \"one2many\",\n relation: \"spreadsheet.dashboard\",\n relation_field: \"dashboard_group_id\",\n },\n },\n records: [\n { id: 1, name: \"Container 1\", dashboard_ids: [1, 2] },\n { id: 2, name: \"Container 2\", dashboard_ids: [3] },\n ],\n },\n },\n views: {},\n };\n}\n", "(function (exports, owl) {\n 'use strict';\n\n /*\n * usage: every string should be translated either with _lt if they are registered with a registry at\n * the load of the app or with Spreadsheet._t in the templates. Spreadsheet._t is exposed in the\n * sub-env of Spreadsheet components as _t\n * */\n // define a mock translation function, when o-spreadsheet runs in standalone it doesn't translate any string\n let _translate = (s) => s;\n function sprintf(s, ...values) {\n if (values.length === 1 && typeof values[0] === \"object\") {\n const valuesDict = values[0];\n s = s.replace(/\\%\\(?([^\\)]+)\\)s/g, (match, value) => valuesDict[value]);\n }\n else if (values.length > 0) {\n s = s.replace(/\\%s/g, () => values.shift());\n }\n return s;\n }\n /***\n * Allow to inject a translation function from outside o-spreadsheet.\n * @param tfn the function that will do the translation\n */\n function setTranslationMethod(tfn) {\n _translate = tfn;\n }\n const _t = function (s, ...values) {\n return sprintf(_translate(s), ...values);\n };\n const _lt = function (str, ...values) {\n // casts the object to unknown then to string to trick typescript into thinking that the object it receives is actually a string\n // this way it will be typed correctly (behaves like a string) but tests like typeof _lt(\"whatever\") will be object and not string !\n return new LazyTranslatedString(str, values);\n };\n class LazyTranslatedString extends String {\n constructor(str, values) {\n super(str);\n this.values = values;\n }\n valueOf() {\n const str = super.valueOf();\n return sprintf(_translate(str), ...this.values);\n }\n toString() {\n return this.valueOf();\n }\n }\n\n var CellErrorType;\n (function (CellErrorType) {\n CellErrorType[\"NotAvailable\"] = \"#N/A\";\n CellErrorType[\"InvalidReference\"] = \"#REF\";\n CellErrorType[\"BadExpression\"] = \"#BAD_EXPR\";\n CellErrorType[\"CircularDependency\"] = \"#CYCLE\";\n CellErrorType[\"UnknownFunction\"] = \"#NAME?\";\n CellErrorType[\"GenericError\"] = \"#ERROR\";\n })(CellErrorType || (CellErrorType = {}));\n var CellErrorLevel;\n (function (CellErrorLevel) {\n CellErrorLevel[CellErrorLevel[\"silent\"] = 0] = \"silent\";\n CellErrorLevel[CellErrorLevel[\"error\"] = 1] = \"error\";\n })(CellErrorLevel || (CellErrorLevel = {}));\n class EvaluationError extends Error {\n constructor(errorType, message, logLevel = CellErrorLevel.error) {\n super(message);\n this.errorType = errorType;\n this.logLevel = logLevel;\n }\n }\n class BadExpressionError extends EvaluationError {\n constructor(errorMessage) {\n super(CellErrorType.BadExpression, errorMessage);\n }\n }\n class CircularDependencyError extends EvaluationError {\n constructor() {\n super(CellErrorType.CircularDependency, _lt(\"Circular reference\"));\n }\n }\n class InvalidReferenceError extends EvaluationError {\n constructor() {\n super(CellErrorType.InvalidReference, _lt(\"Invalid reference\"));\n }\n }\n class NotAvailableError extends EvaluationError {\n constructor(errorMessage = undefined) {\n super(CellErrorType.NotAvailable, errorMessage || _lt(\"Data not available\"), errorMessage ? CellErrorLevel.error : CellErrorLevel.silent);\n }\n }\n class UnknownFunctionError extends EvaluationError {\n constructor(fctName) {\n super(CellErrorType.UnknownFunction, _lt('Unknown function: \"%s\"', fctName));\n }\n }\n\n const CANVAS_SHIFT = 0.5;\n // Colors\n const BACKGROUND_GRAY_COLOR = \"#f5f5f5\";\n const BACKGROUND_HEADER_COLOR = \"#F8F9FA\";\n const BACKGROUND_HEADER_SELECTED_COLOR = \"#E8EAED\";\n const BACKGROUND_HEADER_ACTIVE_COLOR = \"#595959\";\n const TEXT_HEADER_COLOR = \"#666666\";\n const FIGURE_BORDER_COLOR = \"#c9ccd2\";\n const SELECTION_BORDER_COLOR = \"#3266ca\";\n const HEADER_BORDER_COLOR = \"#C0C0C0\";\n const CELL_BORDER_COLOR = \"#E2E3E3\";\n const BACKGROUND_CHART_COLOR = \"#FFFFFF\";\n const MENU_ITEM_DISABLED_COLOR = \"#CACACA\";\n const DEFAULT_COLOR_SCALE_MIDPOINT_COLOR = 0xb6d7a8;\n const LINK_COLOR = \"#01666b\";\n const FILTERS_COLOR = \"#188038\";\n const BACKGROUND_HEADER_FILTER_COLOR = \"#E6F4EA\";\n const BACKGROUND_HEADER_SELECTED_FILTER_COLOR = \"#CEEAD6\";\n // Color picker\n const COLOR_PICKER_DEFAULTS = [\n \"#000000\",\n \"#434343\",\n \"#666666\",\n \"#999999\",\n \"#b7b7b7\",\n \"#cccccc\",\n \"#d9d9d9\",\n \"#efefef\",\n \"#f3f3f3\",\n \"#ffffff\",\n \"#980000\",\n \"#ff0000\",\n \"#ff9900\",\n \"#ffff00\",\n \"#00ff00\",\n \"#00ffff\",\n \"#4a86e8\",\n \"#0000ff\",\n \"#9900ff\",\n \"#ff00ff\",\n \"#e6b8af\",\n \"#f4cccc\",\n \"#fce5cd\",\n \"#fff2cc\",\n \"#d9ead3\",\n \"#d0e0e3\",\n \"#c9daf8\",\n \"#cfe2f3\",\n \"#d9d2e9\",\n \"#ead1dc\",\n \"#dd7e6b\",\n \"#ea9999\",\n \"#f9cb9c\",\n \"#ffe599\",\n \"#b6d7a8\",\n \"#a2c4c9\",\n \"#a4c2f4\",\n \"#9fc5e8\",\n \"#b4a7d6\",\n \"#d5a6bd\",\n \"#cc4125\",\n \"#e06666\",\n \"#f6b26b\",\n \"#ffd966\",\n \"#93c47d\",\n \"#76a5af\",\n \"#6d9eeb\",\n \"#6fa8dc\",\n \"#8e7cc3\",\n \"#c27ba0\",\n \"#a61c00\",\n \"#cc0000\",\n \"#e69138\",\n \"#f1c232\",\n \"#6aa84f\",\n \"#45818e\",\n \"#3c78d8\",\n \"#3d85c6\",\n \"#674ea7\",\n \"#a64d79\",\n \"#85200c\",\n \"#990000\",\n \"#b45f06\",\n \"#bf9000\",\n \"#38761d\",\n \"#134f5c\",\n \"#1155cc\",\n \"#0b5394\",\n \"#351c75\",\n \"#741b47\",\n \"#5b0f00\",\n \"#660000\",\n \"#783f04\",\n \"#7f6000\",\n \"#274e13\",\n \"#0c343d\",\n \"#1c4587\",\n \"#073763\",\n \"#20124d\",\n \"#4c1130\",\n ];\n // Dimensions\n const MIN_ROW_HEIGHT = 10;\n const MIN_COL_WIDTH = 5;\n const HEADER_HEIGHT = 26;\n const HEADER_WIDTH = 48;\n const TOPBAR_HEIGHT = 63;\n const BOTTOMBAR_HEIGHT = 36;\n const DEFAULT_CELL_WIDTH = 96;\n const DEFAULT_CELL_HEIGHT = 23;\n const SCROLLBAR_WIDTH$1 = 15;\n const AUTOFILL_EDGE_LENGTH = 8;\n const ICON_EDGE_LENGTH = 18;\n const UNHIDE_ICON_EDGE_LENGTH = 14;\n const MIN_CF_ICON_MARGIN = 4;\n const MIN_CELL_TEXT_MARGIN = 4;\n const CF_ICON_EDGE_LENGTH = 15;\n const PADDING_AUTORESIZE_VERTICAL = 3;\n const PADDING_AUTORESIZE_HORIZONTAL = MIN_CELL_TEXT_MARGIN;\n const FILTER_ICON_MARGIN = 2;\n const FILTER_ICON_EDGE_LENGTH = 17;\n // Menus\n const MENU_WIDTH = 250;\n const MENU_VERTICAL_PADDING = 8;\n const MENU_ITEM_HEIGHT = 28;\n const MENU_SEPARATOR_BORDER_WIDTH = 1;\n const MENU_SEPARATOR_PADDING = 5;\n const MENU_SEPARATOR_HEIGHT = MENU_SEPARATOR_BORDER_WIDTH + 2 * MENU_SEPARATOR_PADDING;\n const FIGURE_BORDER_SIZE = 1;\n // Fonts\n const DEFAULT_FONT_WEIGHT = \"400\";\n const DEFAULT_FONT_SIZE = 10;\n const HEADER_FONT_SIZE = 11;\n const DEFAULT_FONT = \"'Roboto', arial\";\n // Borders\n const DEFAULT_BORDER_DESC = [\"thin\", \"#000\"];\n const DEFAULT_FILTER_BORDER_DESC = [\"thin\", FILTERS_COLOR];\n // DateTimeRegex\n const DATETIME_FORMAT = /[ymd:]/;\n // Ranges\n const INCORRECT_RANGE_STRING = CellErrorType.InvalidReference;\n // Max Number of history steps kept in memory\n const MAX_HISTORY_STEPS = 99;\n // Id of the first revision\n const DEFAULT_REVISION_ID = \"START_REVISION\";\n // Figure\n const DEFAULT_FIGURE_HEIGHT = 335;\n const DEFAULT_FIGURE_WIDTH = 536;\n // Chart\n const MAX_CHAR_LABEL = 20;\n const FIGURE_ID_SPLITTER = \"??\";\n const DEFAULT_GAUGE_LOWER_COLOR = \"#cc0000\";\n const DEFAULT_GAUGE_MIDDLE_COLOR = \"#f1c232\";\n const DEFAULT_GAUGE_UPPER_COLOR = \"#6aa84f\";\n const DEFAULT_SCORECARD_BASELINE_MODE = \"difference\";\n const DEFAULT_SCORECARD_BASELINE_COLOR_UP = \"#00A04A\";\n const DEFAULT_SCORECARD_BASELINE_COLOR_DOWN = \"#DC6965\";\n const LINE_FILL_TRANSPARENCY = 0.4;\n const MIN_FIG_SIZE = 80;\n // session\n const DEBOUNCE_TIME = 200;\n const MESSAGE_VERSION = 1;\n // Sheets\n const FORBIDDEN_SHEET_CHARS = [\"'\", \"*\", \"?\", \"/\", \"\\\\\", \"[\", \"]\"];\n const FORBIDDEN_IN_EXCEL_REGEX = /'|\\*|\\?|\\/|\\\\|\\[|\\]/;\n // Cells\n const FORMULA_REF_IDENTIFIER = \"|\";\n const DEFAULT_ERROR_MESSAGE = _lt(\"Invalid expression\");\n // Components\n var ComponentsImportance;\n (function (ComponentsImportance) {\n ComponentsImportance[ComponentsImportance[\"Grid\"] = 0] = \"Grid\";\n ComponentsImportance[ComponentsImportance[\"Highlight\"] = 5] = \"Highlight\";\n ComponentsImportance[ComponentsImportance[\"Figure\"] = 10] = \"Figure\";\n ComponentsImportance[ComponentsImportance[\"ScrollBar\"] = 15] = \"ScrollBar\";\n ComponentsImportance[ComponentsImportance[\"Dropdown\"] = 16] = \"Dropdown\";\n ComponentsImportance[ComponentsImportance[\"Composer\"] = 20] = \"Composer\";\n ComponentsImportance[ComponentsImportance[\"ColorPicker\"] = 25] = \"ColorPicker\";\n ComponentsImportance[ComponentsImportance[\"IconPicker\"] = 25] = \"IconPicker\";\n ComponentsImportance[ComponentsImportance[\"Popover\"] = 30] = \"Popover\";\n ComponentsImportance[ComponentsImportance[\"ChartAnchor\"] = 1000] = \"ChartAnchor\";\n })(ComponentsImportance || (ComponentsImportance = {}));\n const DEFAULT_SHEETVIEW_SIZE = 1000;\n const MAXIMAL_FREEZABLE_RATIO = 0.85;\n\n const fontSizes = [\n { pt: 7.5, px: 10 },\n { pt: 8, px: 11 },\n { pt: 9, px: 12 },\n { pt: 10, px: 13 },\n { pt: 10.5, px: 14 },\n { pt: 11, px: 15 },\n { pt: 12, px: 16 },\n { pt: 14, px: 18.7 },\n { pt: 15, px: 20 },\n { pt: 16, px: 21.3 },\n { pt: 18, px: 24 },\n { pt: 22, px: 29.3 },\n { pt: 24, px: 32 },\n { pt: 26, px: 34.7 },\n { pt: 36, px: 48 },\n ];\n const fontSizeMap = {};\n for (let font of fontSizes) {\n fontSizeMap[font.pt] = font.px;\n }\n\n // -----------------------------------------------------------------------------\n // Date Type\n // -----------------------------------------------------------------------------\n // -----------------------------------------------------------------------------\n // Parsing\n // -----------------------------------------------------------------------------\n const INITIAL_1900_DAY = new Date(1899, 11, 30);\n const MS_PER_DAY = 24 * 60 * 60 * 1000;\n const CURRENT_MILLENIAL = 2000; // note: don't forget to update this in 2999\n const CURRENT_YEAR = new Date().getFullYear();\n const INITIAL_JS_DAY = new Date(0);\n const DATE_JS_1900_OFFSET = INITIAL_JS_DAY - INITIAL_1900_DAY;\n const mdyDateRegexp = /^\\d{1,2}(\\/|-|\\s)\\d{1,2}((\\/|-|\\s)\\d{1,4})?$/;\n const ymdDateRegexp = /^\\d{3,4}(\\/|-|\\s)\\d{1,2}(\\/|-|\\s)\\d{1,2}$/;\n const timeRegexp = /((\\d+(:\\d+)?(:\\d+)?\\s*(AM|PM))|(\\d+:\\d+(:\\d+)?))$/;\n function parseDateTime(str) {\n str = str.trim();\n let time;\n const timeMatch = str.match(timeRegexp);\n if (timeMatch) {\n time = parseTime(timeMatch[0]);\n if (time === null) {\n return null;\n }\n str = str.replace(timeMatch[0], \"\").trim();\n }\n let date;\n const mdyDateMatch = str.match(mdyDateRegexp);\n const ymdDateMatch = str.match(ymdDateRegexp);\n if (mdyDateMatch || ymdDateMatch) {\n let dateMatch;\n if (mdyDateMatch) {\n dateMatch = mdyDateMatch[0];\n date = parseDate(dateMatch, \"mdy\");\n }\n else {\n dateMatch = ymdDateMatch[0];\n date = parseDate(dateMatch, \"ymd\");\n }\n if (date === null) {\n return null;\n }\n str = str.replace(dateMatch, \"\").trim();\n }\n if (str !== \"\" || !(date || time)) {\n return null;\n }\n if (date && time) {\n return {\n value: date.value + time.value,\n format: date.format + \" \" + (time.format === \"hhhh:mm:ss\" ? \"hh:mm:ss\" : time.format),\n jsDate: new Date(date.jsDate.getFullYear() + time.jsDate.getFullYear() - 1899, date.jsDate.getMonth() + time.jsDate.getMonth() - 11, date.jsDate.getDate() + time.jsDate.getDate() - 30, date.jsDate.getHours() + time.jsDate.getHours(), date.jsDate.getMinutes() + time.jsDate.getMinutes(), date.jsDate.getSeconds() + time.jsDate.getSeconds()),\n };\n }\n return date || time;\n }\n function parseDate(str, dateFormat) {\n const isMDY = dateFormat === \"mdy\";\n const isYMD = dateFormat === \"ymd\";\n if (isMDY || isYMD) {\n const parts = str.split(/\\/|-|\\s/);\n const monthIndex = isMDY ? 0 : 1;\n const dayIndex = isMDY ? 1 : 2;\n const yearIndex = isMDY ? 2 : 0;\n const month = Number(parts[monthIndex]);\n const day = Number(parts[dayIndex]);\n const leadingZero = (parts[monthIndex].length === 2 && month < 10) || (parts[dayIndex].length === 2 && day < 10);\n const year = parts[yearIndex] ? inferYear(parts[yearIndex]) : CURRENT_YEAR;\n const jsDate = new Date(year, month - 1, day);\n const sep = str.match(/\\/|-|\\s/)[0];\n if (jsDate.getMonth() !== month - 1 || jsDate.getDate() !== day) {\n // invalid date\n return null;\n }\n const delta = jsDate - INITIAL_1900_DAY;\n let format = leadingZero ? `mm${sep}dd` : `m${sep}d`;\n if (parts[yearIndex]) {\n format = isMDY ? format + sep + \"yyyy\" : \"yyyy\" + sep + format;\n }\n return {\n value: Math.round(delta / MS_PER_DAY),\n format: format,\n jsDate,\n };\n }\n return null;\n }\n function inferYear(str) {\n const nbr = Number(str);\n switch (str.length) {\n case 1:\n return CURRENT_MILLENIAL + nbr;\n case 2:\n const offset = CURRENT_MILLENIAL + nbr > CURRENT_YEAR + 10 ? -100 : 0;\n const base = CURRENT_MILLENIAL + offset;\n return base + nbr;\n case 3:\n case 4:\n return nbr;\n }\n return 0;\n }\n function parseTime(str) {\n str = str.trim();\n if (timeRegexp.test(str)) {\n const isAM = /AM/i.test(str);\n const isPM = /PM/i.test(str);\n const strTime = isAM || isPM ? str.substring(0, str.length - 2).trim() : str;\n const parts = strTime.split(/:/);\n const isMinutes = parts.length >= 2;\n const isSeconds = parts.length === 3;\n let hours = Number(parts[0]);\n let minutes = isMinutes ? Number(parts[1]) : 0;\n let seconds = isSeconds ? Number(parts[2]) : 0;\n let format = isSeconds ? \"hh:mm:ss\" : \"hh:mm\";\n if (isAM || isPM) {\n format += \" a\";\n }\n else if (!isMinutes) {\n return null;\n }\n if (hours >= 12 && isAM) {\n hours -= 12;\n }\n else if (hours < 12 && isPM) {\n hours += 12;\n }\n minutes += Math.floor(seconds / 60);\n seconds %= 60;\n hours += Math.floor(minutes / 60);\n minutes %= 60;\n if (hours >= 24) {\n format = \"hhhh:mm:ss\";\n }\n const jsDate = new Date(1899, 11, 30, hours, minutes, seconds);\n return {\n value: hours / 24 + minutes / 1440 + seconds / 86400,\n format: format,\n jsDate: jsDate,\n };\n }\n return null;\n }\n // -----------------------------------------------------------------------------\n // Conversion\n // -----------------------------------------------------------------------------\n function numberToJsDate(value) {\n const truncValue = Math.trunc(value);\n let date = new Date(truncValue * MS_PER_DAY - DATE_JS_1900_OFFSET);\n let time = value - truncValue;\n time = time < 0 ? 1 + time : time;\n const hours = Math.round(time * 24);\n const minutes = Math.round((time - hours / 24) * 24 * 60);\n const seconds = Math.round((time - hours / 24 - minutes / 24 / 60) * 24 * 60 * 60);\n date.setHours(hours);\n date.setMinutes(minutes);\n date.setSeconds(seconds);\n return date;\n }\n function jsDateToRoundNumber(date) {\n const delta = date.getTime() - INITIAL_1900_DAY.getTime();\n return Math.round(delta / MS_PER_DAY);\n }\n /** Return the number of days in the current month of the given date */\n function getDaysInMonth(date) {\n return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();\n }\n function isLastDayOfMonth(date) {\n return getDaysInMonth(date) === date.getDate();\n }\n /**\n * Add a certain number of months to a date. This will adapt the month number, and possibly adapt\n * the day of the month to keep it in the month.\n *\n * For example \"31/12/2020\" minus one month will be \"30/11/2020\", and not \"31/11/2020\"\n *\n * @param keepEndOfMonth if true, if the given date was the last day of a month, the returned date will\n * also always be the last day of a month.\n */\n function addMonthsToDate(date, months, keepEndOfMonth) {\n const yStart = date.getFullYear();\n const mStart = date.getMonth();\n const dStart = date.getDate();\n const jsDate = new Date(yStart, mStart + months);\n if (keepEndOfMonth && dStart === getDaysInMonth(date)) {\n jsDate.setDate(getDaysInMonth(jsDate));\n }\n else if (dStart > getDaysInMonth(jsDate)) {\n // 31/03 minus one month should be 28/02, not 31/02\n jsDate.setDate(getDaysInMonth(jsDate));\n }\n else {\n jsDate.setDate(dStart);\n }\n return jsDate;\n }\n function isLeapYear(year) {\n const _year = Math.trunc(year);\n return (_year % 4 === 0 && _year % 100 != 0) || _year % 400 == 0;\n }\n function getYearFrac(startDate, endDate, _dayCountConvention) {\n if (startDate === endDate) {\n return 0;\n }\n if (startDate > endDate) {\n const stack = endDate;\n endDate = startDate;\n startDate = stack;\n }\n const jsStartDate = numberToJsDate(startDate);\n const jsEndDate = numberToJsDate(endDate);\n let dayStart = jsStartDate.getDate();\n let dayEnd = jsEndDate.getDate();\n const monthStart = jsStartDate.getMonth(); // january is 0\n const monthEnd = jsEndDate.getMonth(); // january is 0\n const yearStart = jsStartDate.getFullYear();\n const yearEnd = jsEndDate.getFullYear();\n let yearsStart = 0;\n let yearsEnd = 0;\n switch (_dayCountConvention) {\n // 30/360 US convention --------------------------------------------------\n case 0:\n if (dayStart === 31)\n dayStart = 30;\n if (dayStart === 30 && dayEnd === 31)\n dayEnd = 30;\n // If jsStartDate is the last day of February\n if (monthStart === 1 && dayStart === (isLeapYear(yearStart) ? 29 : 28)) {\n dayStart = 30;\n // If jsEndDate is the last day of February\n if (monthEnd === 1 && dayEnd === (isLeapYear(yearEnd) ? 29 : 28)) {\n dayEnd = 30;\n }\n }\n yearsStart = yearStart + (monthStart * 30 + dayStart) / 360;\n yearsEnd = yearEnd + (monthEnd * 30 + dayEnd) / 360;\n break;\n // actual/actual convention ----------------------------------------------\n case 1:\n let daysInYear = 365;\n const isSameYear = yearStart === yearEnd;\n const isOneDeltaYear = yearStart + 1 === yearEnd;\n const isMonthEndBigger = monthStart < monthEnd;\n const isSameMonth = monthStart === monthEnd;\n const isDayEndBigger = dayStart < dayEnd;\n // |-----| <-- one Year\n // 'A' is start date\n // 'B' is end date\n if ((!isSameYear && !isOneDeltaYear) ||\n (!isSameYear && isMonthEndBigger) ||\n (!isSameYear && isSameMonth && isDayEndBigger)) {\n // |---A-|-----|-B---| <-- !isSameYear && !isOneDeltaYear\n // |---A-|----B|-----| <-- !isSameYear && isMonthEndBigger\n // |---A-|---B-|-----| <-- !isSameYear && isSameMonth && isDayEndBigger\n let countYears = 0;\n let countDaysInYears = 0;\n for (let y = yearStart; y <= yearEnd; y++) {\n countYears++;\n countDaysInYears += isLeapYear(y) ? 366 : 365;\n }\n daysInYear = countDaysInYears / countYears;\n }\n else if (!isSameYear) {\n // |-AF--|B----|-----|\n if (isLeapYear(yearStart) && monthStart < 2) {\n daysInYear = 366;\n }\n // |--A--|FB---|-----|\n if (isLeapYear(yearEnd) && (monthEnd > 1 || (monthEnd === 1 && dayEnd === 29))) {\n daysInYear = 366;\n }\n }\n else {\n // remaining cases:\n //\n // |-F-AB|-----|-----|\n // |AB-F-|-----|-----|\n // |A-F-B|-----|-----|\n // if February 29 occurs between date1 (exclusive) and date2 (inclusive)\n // daysInYear --> 366\n if (isLeapYear(yearStart)) {\n daysInYear = 366;\n }\n }\n yearsStart = startDate / daysInYear;\n yearsEnd = endDate / daysInYear;\n break;\n // actual/360 convention -------------------------------------------------\n case 2:\n yearsStart = startDate / 360;\n yearsEnd = endDate / 360;\n break;\n // actual/365 convention -------------------------------------------------\n case 3:\n yearsStart = startDate / 365;\n yearsEnd = endDate / 365;\n break;\n // 30/360 European convention --------------------------------------------\n case 4:\n if (dayStart === 31)\n dayStart = 30;\n if (dayEnd === 31)\n dayEnd = 30;\n yearsStart = yearStart + (monthStart * 30 + dayStart) / 360;\n yearsEnd = yearEnd + (monthEnd * 30 + dayEnd) / 360;\n break;\n }\n return yearsEnd - yearsStart;\n }\n\n //------------------------------------------------------------------------------\n /**\n * Stringify an object, like JSON.stringify, except that the first level of keys\n * is ordered.\n */\n function stringify(obj) {\n return JSON.stringify(obj, Object.keys(obj).sort());\n }\n /**\n * Remove quotes from a quoted string\n * ```js\n * removeStringQuotes('\"Hello\"')\n * > 'Hello'\n * ```\n */\n function removeStringQuotes(str) {\n if (str[0] === '\"') {\n str = str.slice(1);\n }\n if (str[str.length - 1] === '\"' && str[str.length - 2] !== \"\\\\\") {\n return str.slice(0, str.length - 1);\n }\n return str;\n }\n function isCloneable(obj) {\n return \"clone\" in obj && obj.clone instanceof Function;\n }\n /**\n * Escapes a string to use as a literal string in a RegExp.\n * @url https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping\n */\n function escapeRegExp(str) {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n }\n /**\n * Deep copy arrays, plain objects and primitive values.\n * Throws an error for other types such as class instances.\n * Sparse arrays remain sparse.\n */\n function deepCopy(obj) {\n const result = Array.isArray(obj) ? [] : {};\n switch (typeof obj) {\n case \"object\": {\n if (obj === null) {\n return obj;\n }\n else if (isCloneable(obj)) {\n return obj.clone();\n }\n else if (!(isPlainObject(obj) || obj instanceof Array)) {\n throw new Error(\"Unsupported type: only objects and arrays are supported\");\n }\n for (const key in obj) {\n result[key] = deepCopy(obj[key]);\n }\n return result;\n }\n case \"number\":\n case \"string\":\n case \"boolean\":\n case \"function\":\n case \"undefined\":\n return obj;\n default:\n throw new Error(`Unsupported type: ${typeof obj}`);\n }\n }\n /**\n * Check if the object is a plain old javascript object.\n */\n function isPlainObject(obj) {\n return typeof obj === \"object\" && (obj === null || obj === void 0 ? void 0 : obj.constructor) === Object;\n }\n /**\n * Sanitize the name of a sheet, by eventually removing quotes\n * @param sheetName name of the sheet, potentially quoted with single quotes\n */\n function getUnquotedSheetName(sheetName) {\n if (sheetName.startsWith(\"'\")) {\n sheetName = sheetName.slice(1, -1).replace(/''/g, \"'\");\n }\n return sheetName;\n }\n /**\n * Add quotes around the sheet name if it contains at least one non alphanumeric character\n * '\\w' captures [0-9][a-z][A-Z] and _.\n * @param sheetName Name of the sheet\n */\n function getComposerSheetName(sheetName) {\n var _a;\n if (((_a = sheetName.match(/\\w/g)) === null || _a === void 0 ? void 0 : _a.length) !== sheetName.length) {\n sheetName = `'${sheetName}'`;\n }\n return sheetName;\n }\n function clip(val, min, max) {\n return val < min ? min : val > max ? max : val;\n }\n function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {\n return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;\n }\n /**\n * Get the default height of the cell given its style.\n */\n function getDefaultCellHeight(style) {\n // TO DO: take multi text line into account to compute the real cell height in case of wrapping cell\n const fontSize = computeTextFontSizeInPixels(style);\n return computeTextLinesHeight(fontSize) + 2 * PADDING_AUTORESIZE_VERTICAL;\n }\n function computeTextWidth(context, text, style) {\n context.save();\n context.font = computeTextFont(style);\n const textWidth = context.measureText(text).width;\n context.restore();\n return textWidth;\n }\n function computeTextFont(style) {\n const italic = style.italic ? \"italic \" : \"\";\n const weight = style.bold ? \"bold\" : DEFAULT_FONT_WEIGHT;\n const size = computeTextFontSizeInPixels(style);\n return `${italic}${weight} ${size}px ${DEFAULT_FONT}`;\n }\n function computeTextFontSizeInPixels(style) {\n const sizeInPt = (style === null || style === void 0 ? void 0 : style.fontSize) || DEFAULT_FONT_SIZE;\n if (!fontSizeMap[sizeInPt]) {\n throw new Error(\"Size of the font is not supported\");\n }\n return fontSizeMap[sizeInPt];\n }\n /**\n * Return the font size that makes the width of a text match the given line width.\n * Minimum font size is 1.\n *\n * @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.\n */\n function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = 0.25) {\n let minFontSize = 1;\n if (getTextWidth(minFontSize) > lineWidth)\n return minFontSize;\n if (getTextWidth(maxFontSize) < lineWidth)\n return maxFontSize;\n // Dichotomic search\n let fontSize = (minFontSize + maxFontSize) / 2;\n let currentTextWidth = getTextWidth(fontSize);\n // Use a maximum number of iterations to be safe, because measuring text isn't 100% precise\n let iterations = 0;\n while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {\n if (currentTextWidth >= lineWidth) {\n maxFontSize = (minFontSize + maxFontSize) / 2;\n }\n else {\n minFontSize = (minFontSize + maxFontSize) / 2;\n }\n fontSize = (minFontSize + maxFontSize) / 2;\n currentTextWidth = getTextWidth(fontSize);\n iterations++;\n }\n return fontSize;\n }\n function computeIconWidth(style) {\n return computeTextFontSizeInPixels(style) + 2 * MIN_CF_ICON_MARGIN;\n }\n /**\n * Create a range from start (included) to end (excluded).\n * range(10, 13) => [10, 11, 12]\n * range(2, 8, 2) => [2, 4, 6]\n */\n function range(start, end, step = 1) {\n if (end <= start && step > 0) {\n return [];\n }\n if (step === 0) {\n throw new Error(\"range() step must not be zero\");\n }\n const length = Math.ceil(Math.abs((end - start) / step));\n const array = Array(length);\n for (let i = 0; i < length; i++) {\n array[i] = start + i * step;\n }\n return array;\n }\n /**\n * Groups consecutive numbers.\n * The input array is assumed to be sorted\n * @param numbers\n */\n function groupConsecutive(numbers) {\n return numbers.reduce((groups, currentRow, index, rows) => {\n if (Math.abs(currentRow - rows[index - 1]) === 1) {\n const lastGroup = groups[groups.length - 1];\n lastGroup.push(currentRow);\n }\n else {\n groups.push([currentRow]);\n }\n return groups;\n }, []);\n }\n /**\n * Create one generator from two generators by linking\n * each item of the first generator to the next item of\n * the second generator.\n *\n * Let's say generator G1 yields A, B, C and generator G2 yields X, Y, Z.\n * The resulting generator of `linkNext(G1, G2)` will yield A', B', C'\n * where `A' = A & {next: Y}`, `B' = B & {next: Z}` and `C' = C & {next: undefined}`\n * @param generator\n * @param nextGenerator\n */\n function* linkNext(generator, nextGenerator) {\n nextGenerator.next();\n for (const item of generator) {\n const nextItem = nextGenerator.next();\n yield {\n ...item,\n next: nextItem.done ? undefined : nextItem.value,\n };\n }\n }\n function isBoolean(str) {\n const upperCased = str.toUpperCase();\n return upperCased === \"TRUE\" || upperCased === \"FALSE\";\n }\n function isDateTime(str) {\n return parseDateTime(str) !== null;\n }\n const MARKDOWN_LINK_REGEX = /^\\[([^\\[]+)\\]\\((.+)\\)$/;\n //link must start with http or https\n //https://stackoverflow.com/a/3809435/4760614\n const WEB_LINK_REGEX = /^https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,4}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)$/;\n function isMarkdownLink(str) {\n return MARKDOWN_LINK_REGEX.test(str);\n }\n /**\n * Check if the string is a web link.\n * e.g. http://odoo.com\n */\n function isWebLink(str) {\n return WEB_LINK_REGEX.test(str);\n }\n /**\n * Build a markdown link from a label and an url\n */\n function markdownLink(label, url) {\n return `[${label}](${url})`;\n }\n function parseMarkdownLink(str) {\n const matches = str.match(MARKDOWN_LINK_REGEX) || [];\n const label = matches[1];\n const url = matches[2];\n if (!label || !url) {\n throw new Error(`Could not parse markdown link ${str}.`);\n }\n return {\n label,\n url,\n };\n }\n const O_SPREADSHEET_LINK_PREFIX = \"o-spreadsheet://\";\n function isSheetUrl(url) {\n return url.startsWith(O_SPREADSHEET_LINK_PREFIX);\n }\n function buildSheetLink(sheetId) {\n return `${O_SPREADSHEET_LINK_PREFIX}${sheetId}`;\n }\n /**\n * Parse a sheet link and return the sheet id\n */\n function parseSheetUrl(sheetLink) {\n if (sheetLink.startsWith(O_SPREADSHEET_LINK_PREFIX)) {\n return sheetLink.substr(O_SPREADSHEET_LINK_PREFIX.length);\n }\n throw new Error(`${sheetLink} is not a valid sheet link`);\n }\n /**\n * This helper function can be used as a type guard when filtering arrays.\n * const foo: number[] = [1, 2, undefined, 4].filter(isDefined)\n */\n function isDefined$1(argument) {\n return argument !== undefined;\n }\n /**\n * Check if all the values of an object, and all the values of the objects inside of it, are undefined.\n */\n function isObjectEmptyRecursive(argument) {\n if (argument === undefined)\n return true;\n return Object.values(argument).every((value) => typeof value === \"object\" ? isObjectEmptyRecursive(value) : !value);\n }\n /**\n * Get the id of the given item (its key in the given dictionnary).\n * If the given item does not exist in the dictionary, it creates one with a new id.\n */\n function getItemId(item, itemsDic) {\n for (let [key, value] of Object.entries(itemsDic)) {\n if (stringify(value) === stringify(item)) {\n return parseInt(key, 10);\n }\n }\n // Generate new Id if the item didn't exist in the dictionary\n const ids = Object.keys(itemsDic);\n const maxId = ids.length === 0 ? 0 : Math.max(...ids.map((id) => parseInt(id, 10)));\n itemsDic[maxId + 1] = item;\n return maxId + 1;\n }\n /**\n * This method comes from owl 1 as it was removed in owl 2\n *\n * Returns a function, that, as long as it continues to be invoked, will not\n * be triggered. The function will be called after it stops being called for\n * N milliseconds. If `immediate` is passed, trigger the function on the\n * leading edge, instead of the trailing.\n *\n * Inspired by https://davidwalsh.name/javascript-debounce-function\n */\n function debounce(func, wait, immediate) {\n let timeout;\n return function () {\n const context = this;\n const args = arguments;\n function later() {\n timeout = null;\n if (!immediate) {\n func.apply(context, args);\n }\n }\n const callNow = immediate && !timeout;\n clearTimeout(timeout);\n timeout = setTimeout(later, wait);\n if (callNow) {\n func.apply(context, args);\n }\n };\n }\n /*\n * Concatenate an array of strings.\n */\n function concat(chars) {\n // ~40% faster than chars.join(\"\")\n let output = \"\";\n for (let i = 0, len = chars.length; i < len; i++) {\n output += chars[i];\n }\n return output;\n }\n /**\n * Lazy value computed by the provided function.\n */\n function lazy(fn) {\n let isMemoized = false;\n let memo;\n const lazyValue = () => {\n if (!isMemoized) {\n memo = fn instanceof Function ? fn() : fn;\n isMemoized = true;\n }\n return memo;\n };\n lazyValue.map = (callback) => lazy(() => callback(lazyValue()));\n return lazyValue;\n }\n /**\n * Find the next defined value after the given index in an array of strings. If there is no defined value\n * after the index, return the closest defined value before the index. Return an empty string if no\n * defined value was found.\n *\n */\n function findNextDefinedValue(arr, index) {\n let value = arr.slice(index).find((val) => val);\n if (!value) {\n value = arr\n .slice(0, index)\n .reverse()\n .find((val) => val);\n }\n return value || \"\";\n }\n /** Get index of first header added by an ADD_COLUMNS_ROWS command */\n function getAddHeaderStartIndex(position, base) {\n return position === \"after\" ? base + 1 : base;\n }\n /**\n * Compare two objects.\n */\n function deepEquals(o1, o2) {\n if (o1 === o2)\n return true;\n if ((o1 && !o2) || (o2 && !o1))\n return false;\n // Objects can have different keys if the values are undefined\n const keys = new Set();\n Object.keys(o1).forEach((key) => keys.add(key));\n Object.keys(o2).forEach((key) => keys.add(key));\n for (let key of keys) {\n if (typeof o1[key] !== typeof o1[key])\n return false;\n if (typeof o1[key] === \"object\") {\n if (!deepEquals(o1[key], o2[key]))\n return false;\n }\n else {\n if (o1[key] !== o2[key])\n return false;\n }\n }\n return true;\n }\n /**\n * Return an object with all the keys in the object that have a falsy value removed.\n */\n function removeFalsyAttributes(obj) {\n const cleanObject = { ...obj };\n Object.keys(cleanObject).forEach((key) => !cleanObject[key] && delete cleanObject[key]);\n return cleanObject;\n }\n /** Transform a string to lower case. If the string is undefined, return an empty string */\n function toLowerCase(str) {\n return str ? str.toLowerCase() : \"\";\n }\n function transpose2dArray(matrix) {\n if (!matrix.length)\n return matrix;\n return matrix[0].map((_, i) => matrix.map((row) => row[i]));\n }\n\n const colors$1 = [\n \"#eb6d00\",\n \"#0074d9\",\n \"#ad8e00\",\n \"#169ed4\",\n \"#b10dc9\",\n \"#00a82d\",\n \"#00a3a3\",\n \"#f012be\",\n \"#3d9970\",\n \"#111111\",\n \"#62A300\",\n \"#ff4136\",\n \"#949494\",\n \"#85144b\",\n \"#001f3f\",\n ];\n /*\n * transform a color number (R * 256^2 + G * 256 + B) into classic hex6 value\n * */\n function colorNumberString(color) {\n return toHex(color.toString(16).padStart(6, \"0\"));\n }\n /**\n * Converts any CSS color value to a standardized hex6 value.\n * Accepts: hex3, hex6, hex8 and rgb (rgba is not supported)\n *\n * toHex(\"#ABC\")\n * >> \"#AABBCC\"\n *\n * toHex(\"#AAAFFF\")\n * >> \"#AAAFFF\"\n *\n * toHex(\"rgb(30, 80, 16)\")\n * >> \"#1E5010\"\n *\n */\n function toHex(color) {\n if (color.includes(\"rgba\")) {\n throw new Error(`rgba() conversion currently not supported: ${color}`);\n }\n if (color.includes(\"rgb\")) {\n return rgbToHex6(color);\n }\n color = color.replace(\"#\", \"\").toUpperCase();\n if (color.length === 3 || color.length === 4) {\n color = color.split(\"\").reduce((acc, h) => acc + h + h, \"\");\n }\n if (color.replace(/[a-f0-9]/gi, \"\") !== \"\") {\n throw new Error(\"invalid color\");\n }\n return \"#\" + color;\n }\n function isColorValid(color) {\n try {\n const { r, g, b, a } = colorToRGBA(color);\n return (isColorValueValid(r) && isColorValueValid(g) && isColorValueValid(b) && isColorValueValid(a));\n }\n catch (error) {\n return false;\n }\n }\n const isColorValueValid = (v) => v >= 0 && v <= 255;\n function rgba(r, g, b, a = 1) {\n const isInvalid = !isColorValueValid(r) || !isColorValueValid(g) || !isColorValueValid(b) || a < 0 || a > 1;\n if (isInvalid) {\n throw new Error(`Invalid RGBA values ${[r, g, b, a]}`);\n }\n return { a, b, g, r };\n }\n /**\n * The relative brightness of a point in the colorspace, normalized to 0 for\n * darkest black and 1 for lightest white.\n * https://www.w3.org/TR/WCAG20/#relativeluminancedef\n */\n function relativeLuminance(color) {\n let { r, g, b } = colorToRGBA(color);\n r /= 255;\n g /= 255;\n b /= 255;\n const toLinearValue = (c) => (c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);\n const R = toLinearValue(r);\n const G = toLinearValue(g);\n const B = toLinearValue(b);\n return 0.2126 * R + 0.7152 * G + 0.0722 * B;\n }\n /**\n * Convert a CSS rgb color string to a standardized hex6 color value.\n *\n * rgbToHex6(\"rgb(30, 80, 16)\")\n * >> \"#1E5010\"\n */\n function rgbToHex6(color) {\n return (\"#\" +\n concat(color\n .slice(4, -1)\n .split(\",\")\n .map((valueString) => parseInt(valueString, 10).toString(16).padStart(2, \"0\"))).toUpperCase());\n }\n /**\n * RGBA to HEX representation (#RRGGBBAA).\n *\n * https://css-tricks.com/converting-color-spaces-in-javascript/\n */\n function rgbaToHex(rgba) {\n let r = rgba.r.toString(16);\n let g = rgba.g.toString(16);\n let b = rgba.b.toString(16);\n let a = Math.round(rgba.a * 255).toString(16);\n if (r.length == 1)\n r = \"0\" + r;\n if (g.length == 1)\n g = \"0\" + g;\n if (b.length == 1)\n b = \"0\" + b;\n if (a.length == 1)\n a = \"0\" + a;\n if (a === \"ff\")\n a = \"\";\n return \"#\" + r + g + b + a;\n }\n /**\n * Color string to RGBA representation\n */\n function colorToRGBA(color) {\n color = toHex(color);\n let r;\n let g;\n let b;\n let a;\n if (color.length === 7) {\n r = parseInt(color[1] + color[2], 16);\n g = parseInt(color[3] + color[4], 16);\n b = parseInt(color[5] + color[6], 16);\n a = 255;\n }\n else if (color.length === 9) {\n r = parseInt(color[1] + color[2], 16);\n g = parseInt(color[3] + color[4], 16);\n b = parseInt(color[5] + color[6], 16);\n a = parseInt(color[7] + color[8], 16);\n }\n else {\n throw new Error(\"Invalid color\");\n }\n a = +(a / 255).toFixed(3);\n return { a, r, g, b };\n }\n /**\n * HSLA to RGBA.\n *\n * https://css-tricks.com/converting-color-spaces-in-javascript/\n */\n function hslaToRGBA(hsla) {\n hsla = { ...hsla };\n // Must be fractions of 1\n hsla.s /= 100;\n hsla.l /= 100;\n let c = (1 - Math.abs(2 * hsla.l - 1)) * hsla.s;\n let x = c * (1 - Math.abs(((hsla.h / 60) % 2) - 1));\n let m = hsla.l - c / 2;\n let r = 0;\n let g = 0;\n let b = 0;\n if (0 <= hsla.h && hsla.h < 60) {\n r = c;\n g = x;\n b = 0;\n }\n else if (60 <= hsla.h && hsla.h < 120) {\n r = x;\n g = c;\n b = 0;\n }\n else if (120 <= hsla.h && hsla.h < 180) {\n r = 0;\n g = c;\n b = x;\n }\n else if (180 <= hsla.h && hsla.h < 240) {\n r = 0;\n g = x;\n b = c;\n }\n else if (240 <= hsla.h && hsla.h < 300) {\n r = x;\n g = 0;\n b = c;\n }\n else if (300 <= hsla.h && hsla.h < 360) {\n r = c;\n g = 0;\n b = x;\n }\n r = Math.round((r + m) * 255);\n g = Math.round((g + m) * 255);\n b = Math.round((b + m) * 255);\n return { a: hsla.a, r, g, b };\n }\n /**\n * HSLA to RGBA.\n *\n * https://css-tricks.com/converting-color-spaces-in-javascript/\n */\n function rgbaToHSLA(rgba) {\n // Make r, g, and b fractions of 1\n const r = rgba.r / 255;\n const g = rgba.g / 255;\n const b = rgba.b / 255;\n // Find greatest and smallest channel values\n let cMin = Math.min(r, g, b);\n let cMax = Math.max(r, g, b);\n let delta = cMax - cMin;\n let h = 0;\n let s = 0;\n let l = 0;\n // Calculate hue\n // No difference\n if (delta == 0)\n h = 0;\n // Red is max\n else if (cMax == r)\n h = ((g - b) / delta) % 6;\n // Green is max\n else if (cMax == g)\n h = (b - r) / delta + 2;\n // Blue is max\n else\n h = (r - g) / delta + 4;\n h = Math.round(h * 60);\n // Make negative hues positive behind 360\u00b0\n if (h < 0)\n h += 360;\n l = (cMax + cMin) / 2;\n // Calculate saturation\n s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));\n // Multiply l and s by 100\n s = +(s * 100).toFixed(1);\n l = +(l * 100).toFixed(1);\n return { a: rgba.a, h, s, l };\n }\n\n /** Reference of a cell (eg. A1, $B$5) */\n const cellReference = new RegExp(/\\$?([A-Z]{1,3})\\$?([0-9]{1,7})/, \"i\");\n // Same as above, but matches the exact string (nothing before or after)\n const singleCellReference = new RegExp(/^\\$?([A-Z]{1,3})\\$?([0-9]{1,7})$/, \"i\");\n /** Reference of a column header (eg. A, AB) */\n const colHeader = new RegExp(/^([A-Z]{1,3})+$/, \"i\");\n /** Reference of a column (eg. A, $CA, Sheet1!B) */\n const colReference = new RegExp(/^\\s*('.+'!|[^']+!)?\\$?([A-Z]{1,3})$/, \"i\");\n /** Reference of a row (eg. 1, 59, Sheet1!9) */\n const rowReference = new RegExp(/^\\s*('.+'!|[^']+!)?\\$?([0-9]{1,7})$/, \"i\");\n /** Reference of a normal range or a full row range (eg. A1:B1, 1:$5, $A2:5) */\n const fullRowXc = /(\\$?[A-Z]{1,3})?\\$?[0-9]{1,7}\\s*:\\s*(\\$?[A-Z]{1,3})?\\$?[0-9]{1,7}\\s*/i;\n /** Reference of a normal range or a column row range (eg. A1:B1, A:$B, $A1:C) */\n const fullColXc = /\\$?[A-Z]{1,3}(\\$?[0-9]{1,7})?\\s*:\\s*\\$?[A-Z]{1,3}(\\$?[0-9]{1,7})?\\s*/i;\n /** Reference of a cell or a range, it can be a bounded range, a full row or a full column */\n const rangeReference = new RegExp(/^\\s*('.+'!|[^']+!)?/.source +\n \"(\" +\n [cellReference.source, fullRowXc.source, fullColXc.source].join(\"|\") +\n \")\" +\n /$/.source, \"i\");\n /**\n * Return true if the given xc is the reference of a column (e.g. A or AC or Sheet1!A)\n */\n function isColReference(xc) {\n return colReference.test(xc);\n }\n /**\n * Return true if the given xc is the reference of a column (e.g. 1 or Sheet1!1)\n */\n function isRowReference(xc) {\n return rowReference.test(xc);\n }\n function isColHeader(str) {\n return colHeader.test(str);\n }\n /**\n * Return true if the given xc is the reference of a single cell,\n * without any specified sheet (e.g. A1)\n */\n function isSingleCellReference(xc) {\n return singleCellReference.test(xc);\n }\n function splitReference(ref) {\n const parts = ref.split(\"!\");\n const xc = parts.pop();\n const sheetName = getUnquotedSheetName(parts.join(\"!\")) || undefined;\n return { sheetName, xc };\n }\n\n //------------------------------------------------------------------------------\n /**\n * Convert a (col) number to the corresponding letter.\n *\n * Examples:\n * 0 => 'A'\n * 25 => 'Z'\n * 26 => 'AA'\n * 27 => 'AB'\n */\n function numberToLetters(n) {\n if (n < 0) {\n throw new Error(`number must be positive. Got ${n}`);\n }\n if (n < 26) {\n return String.fromCharCode(65 + n);\n }\n else {\n return numberToLetters(Math.floor(n / 26) - 1) + numberToLetters(n % 26);\n }\n }\n /**\n * Convert a string (describing a column) to its number value.\n *\n * Examples:\n * 'A' => 0\n * 'Z' => 25\n * 'AA' => 26\n */\n function lettersToNumber(letters) {\n let result = 0;\n const l = letters.length;\n for (let i = 0; i < l; i++) {\n let n = letters.charCodeAt(i) - 65 + (i < l - 1 ? 1 : 0);\n result += n * 26 ** (l - i - 1);\n }\n return result;\n }\n /**\n * Convert a \"XC\" coordinate to cartesian coordinates.\n *\n * Examples:\n * A1 => [0,0]\n * B3 => [1,2]\n *\n * Note: it also accepts lowercase coordinates, but not fixed references\n */\n function toCartesian(xc) {\n xc = xc.toUpperCase().trim();\n const match = xc.match(cellReference);\n if (match !== null) {\n const [m, letters, numbers] = match;\n if (m === xc) {\n const col = lettersToNumber(letters);\n const row = parseInt(numbers, 10) - 1;\n return { col, row };\n }\n }\n throw new Error(`Invalid cell description: ${xc}`);\n }\n /**\n * Convert from cartesian coordinate to the \"XC\" coordinate system.\n *\n * Examples:\n * - 0,0 => A1\n * - 1,2 => B3\n * - 0,0, {colFixed: false, rowFixed: true} => A$1\n * - 1,2, {colFixed: true, rowFixed: false} => $B3\n */\n function toXC(col, row, rangePart = { colFixed: false, rowFixed: false }) {\n return ((rangePart.colFixed ? \"$\" : \"\") +\n numberToLetters(col) +\n (rangePart.rowFixed ? \"$\" : \"\") +\n String(row + 1));\n }\n\n const MAX_DELAY = 140;\n const MIN_DELAY = 20;\n const ACCELERATION = 0.035;\n /**\n * Decreasing exponential function used to determine the \"speed\" of edge-scrolling\n * as the timeout delay.\n *\n * Returns a timeout delay in milliseconds.\n */\n function scrollDelay(value) {\n // decreasing exponential from MAX_DELAY to MIN_DELAY\n return MIN_DELAY + (MAX_DELAY - MIN_DELAY) * Math.exp(-ACCELERATION * (value - 1));\n }\n\n /**\n * This regexp is supposed to be as close as possible as the numberRegexp, but\n * its purpose is to be used by the tokenizer.\n *\n * - it tolerates extra characters at the end. This is useful because the tokenizer\n * only needs to find the number at the start of a string\n * - it does not accept \",\" as thousand separator, because when we tokenize a\n * formula, commas are used to separate arguments\n * - it does not support % symbol, in formulas % is an operator\n */\n const formulaNumberRegexp = /^-?\\d+(\\.?\\d*(e\\d+)?)?|^-?\\.\\d+/;\n const pIntegerAndDecimals = \"(\\\\d+(,\\\\d{3,})*(\\\\.\\\\d*)?)\"; // pattern that match integer number with or without decimal digits\n const pOnlyDecimals = \"(\\\\.\\\\d+)\"; // pattern that match only expression with decimal digits\n const pScientificFormat = \"(e(\\\\+|-)?\\\\d+)?\"; // pattern that match scientific format between zero and one time (should be placed before pPercentFormat)\n const pPercentFormat = \"(\\\\s*%)?\"; // pattern that match percent symbol between zero and one time\n const pNumber = \"(\\\\s*\" + pIntegerAndDecimals + \"|\" + pOnlyDecimals + \")\" + pScientificFormat + pPercentFormat;\n const pMinus = \"(\\\\s*-)?\"; // pattern that match negative symbol between zero and one time\n const pCurrencyFormat = \"(\\\\s*[\\\\$\u20ac])?\";\n const p1 = pMinus + pCurrencyFormat + pNumber;\n const p2 = pMinus + pNumber + pCurrencyFormat;\n const p3 = pCurrencyFormat + pMinus + pNumber;\n const pNumberExp = \"^((\" + [p1, p2, p3].join(\")|(\") + \"))$\";\n const numberRegexp = new RegExp(pNumberExp, \"i\");\n /**\n * Return true if the argument is a \"number string\".\n *\n * Note that \"\" (empty string) does not count as a number string\n */\n function isNumber(value) {\n if (!value)\n return false;\n // TO DO: add regexp for DATE string format (ex match: \"28 02 2020\")\n return numberRegexp.test(value.trim());\n }\n const invaluableSymbolsRegexp = /[,\\$\u20ac]+/g;\n /**\n * Convert a string into a number. It assumes that the string actually represents\n * a number (as determined by the isNumber function)\n *\n * Note that it accepts \"\" (empty string), even though it does not count as a\n * number from the point of view of the isNumber function.\n */\n function parseNumber(str) {\n // remove invaluable characters\n str = str.replace(invaluableSymbolsRegexp, \"\");\n let n = Number(str);\n if (isNaN(n) && str.includes(\"%\")) {\n n = Number(str.split(\"%\")[0]);\n if (!isNaN(n)) {\n return n / 100;\n }\n }\n return n;\n }\n function percentile(values, percent, isInclusive) {\n const sortedValues = [...values].sort((a, b) => a - b);\n let percentIndex = (sortedValues.length + (isInclusive ? -1 : 1)) * percent;\n if (!isInclusive) {\n percentIndex--;\n }\n if (Number.isInteger(percentIndex)) {\n return sortedValues[percentIndex];\n }\n const indexSup = Math.ceil(percentIndex);\n const indexLow = Math.floor(percentIndex);\n return (sortedValues[indexSup] * (percentIndex - indexLow) +\n sortedValues[indexLow] * (indexSup - percentIndex));\n }\n\n /**\n * Constant used to indicate the maximum of digits that is possible to display\n * in a cell with standard size.\n */\n const MAX_DECIMAL_PLACES = 20;\n /**\n * Number of digits for the default number format. This number of digit make a number fit well in a cell\n * with default size and default font size.\n */\n const DEFAULT_FORMAT_NUMBER_OF_DIGITS = 11;\n //from https://stackoverflow.com/questions/721304/insert-commas-into-number-string @Thomas/Alan Moore\n const thousandsGroupsRegexp = /(\\d+?)(?=(\\d{3})+(?!\\d)|$)/g;\n const zeroRegexp = /0/g;\n // TODO in the future : remove these constants MONTHS/DAYS, and use a library such as luxon to handle it\n // + possibly handle automatic translation of day/month\n const MONTHS = {\n 0: _lt(\"January\"),\n 1: _lt(\"February\"),\n 2: _lt(\"March\"),\n 3: _lt(\"April\"),\n 4: _lt(\"May\"),\n 5: _lt(\"June\"),\n 6: _lt(\"July\"),\n 7: _lt(\"August\"),\n 8: _lt(\"September\"),\n 9: _lt(\"October\"),\n 10: _lt(\"November\"),\n 11: _lt(\"December\"),\n };\n const DAYS$1 = {\n 0: _lt(\"Sunday\"),\n 1: _lt(\"Monday\"),\n 2: _lt(\"Tuesday\"),\n 3: _lt(\"Wednesday\"),\n 4: _lt(\"Thursday\"),\n 5: _lt(\"Friday\"),\n 6: _lt(\"Saturday\"),\n };\n // -----------------------------------------------------------------------------\n // FORMAT REPRESENTATION CACHE\n // -----------------------------------------------------------------------------\n const internalFormatByFormatString = {};\n function parseFormat(formatString) {\n let internalFormat = internalFormatByFormatString[formatString];\n if (internalFormat === undefined) {\n internalFormat = convertFormatToInternalFormat(formatString);\n internalFormatByFormatString[formatString] = internalFormat;\n }\n return internalFormat;\n }\n // -----------------------------------------------------------------------------\n // APPLY FORMAT\n // -----------------------------------------------------------------------------\n /**\n * Formats a cell value with its format.\n */\n function formatValue(value, format) {\n switch (typeof value) {\n case \"string\":\n return value;\n case \"boolean\":\n return value ? \"TRUE\" : \"FALSE\";\n case \"number\":\n // transform to internalNumberFormat\n if (!format) {\n format = createDefaultFormat(value);\n }\n const internalFormat = parseFormat(format);\n return applyInternalFormat(value, internalFormat);\n case \"object\":\n return \"0\";\n }\n }\n function applyInternalFormat(value, internalFormat) {\n if (internalFormat[0].type === \"DATE\") {\n return applyDateTimeFormat(value, internalFormat[0].format);\n }\n let formattedValue = value < 0 ? \"-\" : \"\";\n for (let part of internalFormat) {\n switch (part.type) {\n case \"NUMBER\":\n formattedValue += applyInternalNumberFormat(Math.abs(value), part.format);\n break;\n case \"STRING\":\n formattedValue += part.format;\n break;\n }\n }\n return formattedValue;\n }\n function applyInternalNumberFormat(value, format) {\n if (format.isPercent) {\n value = value * 100;\n }\n value = value / format.magnitude;\n let maxDecimals = 0;\n if (format.decimalPart !== undefined) {\n maxDecimals = format.decimalPart.length;\n }\n const { integerDigits, decimalDigits } = splitNumber(value, maxDecimals);\n let formattedValue = applyIntegerFormat(integerDigits, format.integerPart, format.thousandsSeparator);\n if (format.decimalPart !== undefined) {\n formattedValue += \".\" + applyDecimalFormat(decimalDigits || \"\", format.decimalPart);\n }\n if (format.isPercent) {\n formattedValue += \"%\";\n }\n return formattedValue;\n }\n function applyIntegerFormat(integerDigits, integerFormat, hasSeparator) {\n var _a;\n const _integerDigits = integerDigits === \"0\" ? \"\" : integerDigits;\n let formattedInteger = _integerDigits;\n const delta = integerFormat.length - _integerDigits.length;\n if (delta > 0) {\n // ex: format = \"0#000000\" and integerDigit: \"123\"\n const restIntegerFormat = integerFormat.substring(0, delta); // restIntegerFormat = \"0#00\"\n const countZero = (restIntegerFormat.match(zeroRegexp) || []).length; // countZero = 3\n formattedInteger = \"0\".repeat(countZero) + formattedInteger; // return \"000123\"\n }\n if (hasSeparator) {\n formattedInteger = ((_a = formattedInteger.match(thousandsGroupsRegexp)) === null || _a === void 0 ? void 0 : _a.join(\",\")) || formattedInteger;\n }\n return formattedInteger;\n }\n function applyDecimalFormat(decimalDigits, decimalFormat) {\n // assume the format is valid (no commas)\n let formattedDecimals = decimalDigits;\n if (decimalFormat.length - decimalDigits.length > 0) {\n const restDecimalFormat = decimalFormat.substring(decimalDigits.length, decimalFormat.length + 1);\n const countZero = (restDecimalFormat.match(zeroRegexp) || []).length;\n formattedDecimals = formattedDecimals + \"0\".repeat(countZero);\n }\n return formattedDecimals;\n }\n /**\n * this is a cache that can contains number representation formats\n * from 0 (minimum) to 20 (maximum) digits after the decimal point\n */\n const numberRepresentation = [];\n /** split a number into two strings that contain respectively:\n * - all digit stored in the integer part of the number\n * - all digit stored in the decimal part of the number\n *\n * The 'maxDecimal' parameter allows to indicate the number of digits to not\n * exceed in the decimal part, in which case digits are rounded\n *\n * Intl.Numberformat is used to properly handle all the roundings.\n * e.g. 1234.7 with format ### (<> maxDecimals=0) should become 1235, not 1234\n **/\n function splitNumber(value, maxDecimals = MAX_DECIMAL_PLACES) {\n let formatter = numberRepresentation[maxDecimals];\n if (!formatter) {\n formatter = new Intl.NumberFormat(\"en-US\", {\n maximumFractionDigits: maxDecimals,\n useGrouping: false,\n });\n numberRepresentation[maxDecimals] = formatter;\n }\n const [integerDigits, decimalDigits] = formatter.format(value).split(\".\");\n return { integerDigits, decimalDigits };\n }\n /** Convert a number into a string, without scientific notation */\n function numberToString(number) {\n const { integerDigits, decimalDigits } = splitNumber(number, 20);\n return decimalDigits ? `${integerDigits}.${decimalDigits}` : integerDigits;\n }\n /**\n * Check if the given format is a time, date or date time format.\n */\n function isDateTimeFormat(format) {\n try {\n applyDateTimeFormat(1, format);\n return true;\n }\n catch (error) {\n return false;\n }\n }\n function applyDateTimeFormat(value, format) {\n // TODO: unify the format functions for date and datetime\n // This requires some code to 'parse' or 'tokenize' the format, keep it in a\n // cache, and use it in a single mapping, that recognizes the special list\n // of tokens dd,d,m,y,h, ... and preserves the rest\n const jsDate = numberToJsDate(value);\n const indexH = format.indexOf(\"h\");\n let strDate = \"\";\n let strTime = \"\";\n if (indexH > 0) {\n strDate = formatJSDate(jsDate, format.substring(0, indexH - 1));\n strTime = formatJSTime(jsDate, format.substring(indexH));\n }\n else if (indexH === 0) {\n strTime = formatJSTime(jsDate, format);\n }\n else if (indexH < 0) {\n strDate = formatJSDate(jsDate, format);\n }\n return strDate + (strDate && strTime ? \" \" : \"\") + strTime;\n }\n function formatJSDate(jsDate, format) {\n var _a;\n const sep = (_a = format.match(/\\/|-|\\s/)) === null || _a === void 0 ? void 0 : _a[0];\n const parts = sep ? format.split(sep) : [format];\n return parts\n .map((p) => {\n switch (p) {\n case \"d\":\n return jsDate.getDate();\n case \"dd\":\n return jsDate.getDate().toString().padStart(2, \"0\");\n case \"ddd\":\n return DAYS$1[jsDate.getDay()].slice(0, 3);\n case \"dddd\":\n return DAYS$1[jsDate.getDay()];\n case \"m\":\n return jsDate.getMonth() + 1;\n case \"mm\":\n return String(jsDate.getMonth() + 1).padStart(2, \"0\");\n case \"mmm\":\n return MONTHS[jsDate.getMonth()].slice(0, 3);\n case \"mmmm\":\n return MONTHS[jsDate.getMonth()];\n case \"mmmmm\":\n return MONTHS[jsDate.getMonth()].slice(0, 1);\n case \"yy\":\n const fullYear = String(jsDate.getFullYear()).replace(\"-\", \"\").padStart(2, \"0\");\n return fullYear.slice(fullYear.length - 2);\n case \"yyyy\":\n return jsDate.getFullYear();\n default:\n throw new Error(`invalid format: ${format}`);\n }\n })\n .join(sep);\n }\n function formatJSTime(jsDate, format) {\n let parts = format.split(/:|\\s/);\n const dateHours = jsDate.getHours();\n const isMeridian = parts[parts.length - 1] === \"a\";\n let hours = dateHours;\n let meridian = \"\";\n if (isMeridian) {\n hours = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;\n meridian = dateHours >= 12 ? \" PM\" : \" AM\";\n parts.pop();\n }\n return (parts\n .map((p) => {\n switch (p) {\n case \"hhhh\":\n const helapsedHours = Math.floor((jsDate.getTime() - INITIAL_1900_DAY) / (60 * 60 * 1000));\n return helapsedHours.toString();\n case \"hh\":\n return hours.toString().padStart(2, \"0\");\n case \"mm\":\n return jsDate.getMinutes().toString().padStart(2, \"0\");\n case \"ss\":\n return jsDate.getSeconds().toString().padStart(2, \"0\");\n default:\n throw new Error(`invalid format: ${format}`);\n }\n })\n .join(\":\") + meridian);\n }\n // -----------------------------------------------------------------------------\n // CREATE / MODIFY FORMAT\n // -----------------------------------------------------------------------------\n /**\n * Create a default format for a number.\n *\n * If possible this will try round the number to have less than DEFAULT_FORMAT_NUMBER_OF_DIGITS characters\n * in the number. This is obviously only possible for number with a big decimal part. For number with a lot\n * of digits in the integer part, keep the number as it is.\n */\n function createDefaultFormat(value) {\n let { integerDigits, decimalDigits } = splitNumber(value);\n if (!decimalDigits)\n return \"0\";\n const digitsInIntegerPart = integerDigits.replace(\"-\", \"\").length;\n // If there's no space for at least the decimal separator + a decimal digit, don't display decimals\n if (digitsInIntegerPart + 2 > DEFAULT_FORMAT_NUMBER_OF_DIGITS) {\n return \"0\";\n }\n // -1 for the decimal separator character\n const spaceForDecimalsDigits = DEFAULT_FORMAT_NUMBER_OF_DIGITS - digitsInIntegerPart - 1;\n ({ decimalDigits } = splitNumber(value, Math.min(spaceForDecimalsDigits, decimalDigits.length)));\n return decimalDigits ? \"0.\" + \"0\".repeat(decimalDigits.length) : \"0\";\n }\n function detectFormat(content) {\n if (isDateTime(content)) {\n const internalDate = parseDateTime(content);\n return internalDate.format;\n }\n if (!isNumber(content)) {\n return undefined;\n }\n const digitBase = content.includes(\".\") ? \"0.00\" : \"0\";\n const matchedCurrencies = content.match(/[\\$\u20ac]/);\n if (matchedCurrencies) {\n const matchedFirstDigit = content.match(/[\\d]/);\n const currency = \"[$\" + matchedCurrencies.values().next().value + \"]\";\n if (matchedFirstDigit.index < matchedCurrencies.index) {\n return \"#,##\" + digitBase + currency;\n }\n return currency + \"#,##\" + digitBase;\n }\n if (content.includes(\"%\")) {\n return digitBase + \"%\";\n }\n return undefined;\n }\n function createLargeNumberFormat(format, magnitude, postFix) {\n const internalFormat = parseFormat(format || \"#,##0\");\n const largeNumberFormat = internalFormat\n .map((formatPart) => {\n if (formatPart.type === \"NUMBER\") {\n return [\n {\n ...formatPart,\n format: {\n ...formatPart.format,\n magnitude,\n decimalPart: undefined,\n },\n },\n {\n type: \"STRING\",\n format: postFix,\n },\n ];\n }\n return formatPart;\n })\n .flat();\n return convertInternalFormatToFormat(largeNumberFormat);\n }\n function changeDecimalPlaces(format, step) {\n const internalFormat = parseFormat(format);\n const newInternalFormat = internalFormat.map((intFmt) => {\n if (intFmt.type === \"NUMBER\") {\n return { ...intFmt, format: changeInternalNumberFormatDecimalPlaces(intFmt.format, step) };\n }\n else {\n return intFmt;\n }\n });\n const newFormat = convertInternalFormatToFormat(newInternalFormat);\n internalFormatByFormatString[newFormat] = newInternalFormat;\n return newFormat;\n }\n function changeInternalNumberFormatDecimalPlaces(format, step) {\n var _a;\n const _format = { ...format };\n const sign = Math.sign(step);\n const decimalLength = ((_a = _format.decimalPart) === null || _a === void 0 ? void 0 : _a.length) || 0;\n const countZero = Math.min(Math.max(0, decimalLength + sign), MAX_DECIMAL_PLACES);\n _format.decimalPart = \"0\".repeat(countZero);\n if (_format.decimalPart === \"\") {\n delete _format.decimalPart;\n }\n return _format;\n }\n // -----------------------------------------------------------------------------\n // MANAGING FORMAT\n // -----------------------------------------------------------------------------\n /**\n * Validates the provided format string and returns an InternalFormat Object.\n */\n function convertFormatToInternalFormat(format) {\n if (format === \"\") {\n throw new Error(\"A format cannot be empty\");\n }\n let currentIndex = 0;\n let result = [];\n while (currentIndex < format.length) {\n let closingIndex;\n if (format.charAt(currentIndex) === \"[\") {\n if (format.charAt(currentIndex + 1) !== \"$\") {\n throw new Error(`Currency formats have to be prefixed by a $: ${format}`);\n }\n // manage brackets/customStrings\n closingIndex = format.substring(currentIndex + 1).indexOf(\"]\") + currentIndex + 2;\n if (closingIndex === 0) {\n throw new Error(`Invalid currency brackets format: ${format}`);\n }\n const str = format.substring(currentIndex + 2, closingIndex - 1);\n if (str.includes(\"[\")) {\n throw new Error(`Invalid currency format: ${format}`);\n }\n result.push({\n type: \"STRING\",\n format: str,\n }); // remove leading \"[$\"\" and ending \"]\".\n }\n else {\n // rest of the time\n const nextPartIndex = format.substring(currentIndex).indexOf(\"[\");\n closingIndex = nextPartIndex > -1 ? nextPartIndex + currentIndex : format.length;\n const subFormat = format.substring(currentIndex, closingIndex);\n if (subFormat.match(DATETIME_FORMAT)) {\n result.push({ type: \"DATE\", format: subFormat });\n }\n else {\n result.push({\n type: \"NUMBER\",\n format: convertToInternalNumberFormat(subFormat),\n });\n }\n }\n currentIndex = closingIndex;\n }\n return result;\n }\n const magnitudeRegex = /,*?$/;\n /**\n * @param format a formatString that is only applicable to numbers. I.e. composed of characters 0 # , . %\n */\n function convertToInternalNumberFormat(format) {\n var _a;\n format = format.trim();\n if (containsInvalidNumberChars(format)) {\n throw new Error(`Invalid number format: ${format}`);\n }\n const isPercent = format.includes(\"%\");\n const magnitudeCommas = ((_a = format.match(magnitudeRegex)) === null || _a === void 0 ? void 0 : _a[0]) || \"\";\n const magnitude = !magnitudeCommas ? 1 : 1000 ** magnitudeCommas.length;\n let _format = format.slice(0, format.length - (magnitudeCommas.length || 0));\n const thousandsSeparator = _format.includes(\",\");\n if (_format.match(/\\..*,/)) {\n throw new Error(\"A format can't contain ',' symbol in the decimal part\");\n }\n _format = _format.replace(\"%\", \"\").replace(\",\", \"\");\n const extraSigns = _format.match(/[\\%|,]/);\n if (extraSigns) {\n throw new Error(`A format can only contain a single '${extraSigns[0]}' symbol`);\n }\n const [integerPart, decimalPart] = _format.split(\".\");\n if (decimalPart && decimalPart.length > 20) {\n throw new Error(\"A format can't contain more than 20 decimal places\");\n }\n if (decimalPart !== undefined) {\n return {\n integerPart,\n isPercent,\n thousandsSeparator,\n decimalPart,\n magnitude,\n };\n }\n else {\n return {\n integerPart,\n isPercent,\n thousandsSeparator,\n magnitude,\n };\n }\n }\n const validNumberChars = /[,#0.%]/g;\n function containsInvalidNumberChars(format) {\n return Boolean(format.replace(validNumberChars, \"\"));\n }\n function convertInternalFormatToFormat(internalFormat) {\n let format = \"\";\n for (let part of internalFormat) {\n let currentFormat;\n switch (part.type) {\n case \"NUMBER\":\n const fmt = part.format;\n currentFormat = fmt.integerPart;\n if (fmt.thousandsSeparator) {\n currentFormat = currentFormat.slice(0, -3) + \",\" + currentFormat.slice(-3);\n }\n if (fmt.decimalPart !== undefined) {\n currentFormat += \".\" + fmt.decimalPart;\n }\n if (fmt.isPercent) {\n currentFormat += \"%\";\n }\n if (fmt.magnitude) {\n currentFormat += \",\".repeat(Math.log10(fmt.magnitude) / 3);\n }\n break;\n case \"STRING\":\n currentFormat = `[$${part.format}]`;\n break;\n case \"DATE\":\n currentFormat = part.format;\n break;\n }\n format += currentFormat;\n }\n return format;\n }\n\n class RangeImpl {\n constructor(args, getSheetSize) {\n this.getSheetSize = getSheetSize;\n this.prefixSheet = false;\n this._zone = args.zone;\n this.parts = args.parts;\n this.prefixSheet = args.prefixSheet;\n this.invalidXc = args.invalidXc;\n this.sheetId = args.sheetId;\n this.invalidSheetName = args.invalidSheetName;\n }\n static fromRange(range, getters) {\n if (range instanceof RangeImpl) {\n return range;\n }\n return new RangeImpl(range, getters.getSheetSize);\n }\n get unboundedZone() {\n return this._zone;\n }\n get zone() {\n const { left, top, bottom, right } = this._zone;\n if (right !== undefined && bottom !== undefined)\n return { left, top, right, bottom };\n else if (bottom === undefined && right !== undefined) {\n return { right, top, left, bottom: this.getSheetSize(this.sheetId).height - 1 };\n }\n else if (right === undefined && bottom !== undefined) {\n return { bottom, left, top, right: this.getSheetSize(this.sheetId).width - 1 };\n }\n throw new Error(_lt(\"Bad zone format\"));\n }\n static getRangeParts(xc, zone) {\n const parts = xc.split(\":\").map((p) => {\n const isFullRow = isRowReference(p);\n return {\n colFixed: isFullRow ? false : p.startsWith(\"$\"),\n rowFixed: isFullRow ? p.startsWith(\"$\") : p.includes(\"$\", 1),\n };\n });\n const isFullCol = zone.bottom === undefined;\n const isFullRow = zone.right === undefined;\n if (isFullCol) {\n parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;\n parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;\n if (zone.left === zone.right) {\n parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;\n parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;\n }\n }\n if (isFullRow) {\n parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;\n parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;\n if (zone.top === zone.bottom) {\n parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;\n parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;\n }\n }\n return parts;\n }\n get isFullCol() {\n return this._zone.bottom === undefined;\n }\n get isFullRow() {\n return this._zone.right === undefined;\n }\n get rangeData() {\n return {\n _zone: this._zone,\n _sheetId: this.sheetId,\n };\n }\n /**\n * Check that a zone is valid regarding the order of top-bottom and left-right.\n * Left should be smaller than right, top should be smaller than bottom.\n * If it's not the case, simply invert them, and invert the linked parts\n */\n orderZone() {\n var _a, _b, _c, _d, _e, _f, _g, _h;\n const zone = { ...this._zone };\n let parts = this.parts;\n if (zone.right !== undefined && zone.right < zone.left) {\n let right = zone.right;\n zone.right = zone.left;\n zone.left = right;\n parts = [\n {\n colFixed: ((_a = parts[1]) === null || _a === void 0 ? void 0 : _a.colFixed) || false,\n rowFixed: ((_b = parts[0]) === null || _b === void 0 ? void 0 : _b.rowFixed) || false,\n },\n {\n colFixed: ((_c = parts[0]) === null || _c === void 0 ? void 0 : _c.colFixed) || false,\n rowFixed: ((_d = parts[1]) === null || _d === void 0 ? void 0 : _d.rowFixed) || false,\n },\n ];\n }\n if (zone.bottom !== undefined && zone.bottom < zone.top) {\n let bottom = zone.bottom;\n zone.bottom = zone.top;\n zone.top = bottom;\n parts = [\n {\n colFixed: ((_e = parts[0]) === null || _e === void 0 ? void 0 : _e.colFixed) || false,\n rowFixed: ((_f = parts[1]) === null || _f === void 0 ? void 0 : _f.rowFixed) || false,\n },\n {\n colFixed: ((_g = parts[1]) === null || _g === void 0 ? void 0 : _g.colFixed) || false,\n rowFixed: ((_h = parts[0]) === null || _h === void 0 ? void 0 : _h.rowFixed) || false,\n },\n ];\n }\n return this.clone({ zone, parts });\n }\n /**\n *\n * @param rangeParams optional, values to put in the cloned range instead of the current values of the range\n */\n clone(rangeParams) {\n return new RangeImpl({\n zone: (rangeParams === null || rangeParams === void 0 ? void 0 : rangeParams.zone) ? rangeParams.zone : { ...this._zone },\n sheetId: (rangeParams === null || rangeParams === void 0 ? void 0 : rangeParams.sheetId) ? rangeParams.sheetId : this.sheetId,\n invalidSheetName: rangeParams && \"invalidSheetName\" in rangeParams // 'attr in obj' instead of just 'obj.attr' because we accept undefined values\n ? rangeParams.invalidSheetName\n : this.invalidSheetName,\n invalidXc: rangeParams && \"invalidXc\" in rangeParams ? rangeParams.invalidXc : this.invalidXc,\n parts: (rangeParams === null || rangeParams === void 0 ? void 0 : rangeParams.parts)\n ? rangeParams.parts\n : this.parts.map((part) => {\n return { rowFixed: part.rowFixed, colFixed: part.colFixed };\n }),\n prefixSheet: (rangeParams === null || rangeParams === void 0 ? void 0 : rangeParams.prefixSheet) ? rangeParams.prefixSheet : this.prefixSheet,\n }, this.getSheetSize);\n }\n }\n /**\n * Copy a range. If the range is on the sheetIdFrom, the range will target\n * sheetIdTo.\n */\n function copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, range) {\n const sheetId = range.sheetId === sheetIdFrom ? sheetIdTo : range.sheetId;\n return range.clone({ sheetId });\n }\n /**\n * Create a range from a xc. If the xc is empty, this function returns undefined.\n */\n function createRange(getters, sheetId, range) {\n return range ? getters.getRangeFromSheetXC(sheetId, range) : undefined;\n }\n\n /** Methods from Odoo Web Utils */\n /**\n * This function computes a score that represent the fact that the\n * string contains the pattern, or not\n *\n * - If the score is 0, the string does not contain the letters of the pattern in\n * the correct order.\n * - if the score is > 0, it actually contains the letters.\n *\n * Better matches will get a higher score: consecutive letters are better,\n * and a match closer to the beginning of the string is also scored higher.\n */\n function fuzzyMatch(pattern, str) {\n pattern = pattern.toLocaleLowerCase();\n str = str.toLocaleLowerCase();\n let totalScore = 0;\n let currentScore = 0;\n let len = str.length;\n let patternIndex = 0;\n for (let i = 0; i < len; i++) {\n if (str[i] === pattern[patternIndex]) {\n patternIndex++;\n currentScore += 100 + currentScore - i / 200;\n }\n else {\n currentScore = 0;\n }\n totalScore = totalScore + currentScore;\n }\n return patternIndex === pattern.length ? totalScore : 0;\n }\n /**\n * Return a list of things that matches a pattern, ordered by their 'score' (\n * higher score first). An higher score means that the match is better. For\n * example, consecutive letters are considered a better match.\n */\n function fuzzyLookup(pattern, list, fn) {\n const results = [];\n list.forEach((data) => {\n const score = fuzzyMatch(pattern, fn(data));\n if (score > 0) {\n results.push({ score, elem: data });\n }\n });\n // we want better matches first\n results.sort((a, b) => b.score - a.score);\n return results.map((r) => r.elem);\n }\n\n function createDefaultRows(rowNumber) {\n const rows = [];\n for (let i = 0; i < rowNumber; i++) {\n const row = {\n cells: {},\n };\n rows.push(row);\n }\n return rows;\n }\n\n /*\n * https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript\n * */\n class UuidGenerator {\n constructor() {\n this.isFastIdStrategy = false;\n this.fastIdStart = 0;\n }\n setIsFastStrategy(isFast) {\n this.isFastIdStrategy = isFast;\n }\n uuidv4() {\n if (this.isFastIdStrategy) {\n this.fastIdStart++;\n return String(this.fastIdStart);\n //@ts-ignore\n }\n else if (window.crypto && window.crypto.getRandomValues) {\n //@ts-ignore\n return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16));\n }\n else {\n // mainly for jest and other browsers that do not have the crypto functionality\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, function (c) {\n var r = (Math.random() * 16) | 0, v = c == \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n }\n }\n }\n\n /**\n * Convert from a cartesian reference to a Zone\n * The range boundaries will be kept in the same order as the\n * ones in the text.\n * Examples:\n * \"A1\" ==> Top 0, Bottom 0, Left: 0, Right: 0\n * \"B1:B3\" ==> Top 0, Bottom 3, Left: 1, Right: 1\n * \"Sheet1!A1\" ==> Top 0, Bottom 0, Left: 0, Right: 0\n * \"Sheet1!B1:B3\" ==> Top 0, Bottom 3, Left: 1, Right: 1\n * \"C3:A1\" ==> Top 2, Bottom 0, Left 2, Right 0\n * \"A:A\" ==> Top 0, Bottom undefined, Left 0, Right 0\n * \"A:B3\" or \"B3:A\" ==> Top 2, Bottom undefined, Left 0, Right 1\n *\n * @param xc the string reference to convert\n *\n */\n function toZoneWithoutBoundaryChanges(xc) {\n xc = xc.split(\"!\").pop();\n const ranges = xc\n .replace(/\\$/g, \"\")\n .split(\":\")\n .map((x) => x.trim());\n let top, bottom, left, right;\n let fullCol = false;\n let fullRow = false;\n let hasHeader = false;\n const firstRangePart = ranges[0];\n const secondRangePart = ranges[1] && ranges[1];\n if (isColReference(firstRangePart)) {\n left = right = lettersToNumber(firstRangePart);\n top = bottom = 0;\n fullCol = true;\n }\n else if (isRowReference(firstRangePart)) {\n top = bottom = parseInt(firstRangePart, 10) - 1;\n left = right = 0;\n fullRow = true;\n }\n else {\n const c = toCartesian(firstRangePart);\n left = right = c.col;\n top = bottom = c.row;\n hasHeader = true;\n }\n if (ranges.length === 2) {\n if (isColReference(secondRangePart)) {\n right = lettersToNumber(secondRangePart);\n fullCol = true;\n }\n else if (isRowReference(secondRangePart)) {\n bottom = parseInt(secondRangePart, 10) - 1;\n fullRow = true;\n }\n else {\n const c = toCartesian(secondRangePart);\n right = c.col;\n bottom = c.row;\n top = fullCol ? bottom : top;\n left = fullRow ? right : left;\n hasHeader = true;\n }\n }\n if (fullCol && fullRow) {\n throw new Error(\"Wrong zone xc. The zone cannot be at the same time a full column and a full row\");\n }\n const zone = {\n top,\n left,\n bottom: fullCol ? undefined : bottom,\n right: fullRow ? undefined : right,\n };\n hasHeader = hasHeader && (fullRow || fullCol);\n if (hasHeader) {\n zone.hasHeader = hasHeader;\n }\n return zone;\n }\n /**\n * Convert from a cartesian reference to a (possibly unbounded) Zone\n *\n * Examples:\n * \"A1\" ==> Top 0, Bottom 0, Left: 0, Right: 0\n * \"B1:B3\" ==> Top 0, Bottom 3, Left: 1, Right: 1\n * \"B:B\" ==> Top 0, Bottom undefined, Left: 1, Right: 1\n * \"B2:B\" ==> Top 1, Bottom undefined, Left: 1, Right: 1, hasHeader: 1\n * \"Sheet1!A1\" ==> Top 0, Bottom 0, Left: 0, Right: 0\n * \"Sheet1!B1:B3\" ==> Top 0, Bottom 3, Left: 1, Right: 1\n *\n * @param xc the string reference to convert\n *\n */\n function toUnboundedZone(xc) {\n const zone = toZoneWithoutBoundaryChanges(xc);\n if (zone.right !== undefined && zone.right < zone.left) {\n const tmp = zone.left;\n zone.left = zone.right;\n zone.right = tmp;\n }\n if (zone.bottom !== undefined && zone.bottom < zone.top) {\n const tmp = zone.top;\n zone.top = zone.bottom;\n zone.bottom = tmp;\n }\n return zone;\n }\n /**\n * Convert from a cartesian reference to a Zone.\n * Will return throw an error if given a unbounded zone (eg : A:A).\n *\n * Examples:\n * \"A1\" ==> Top 0, Bottom 0, Left: 0, Right: 0\n * \"B1:B3\" ==> Top 0, Bottom 3, Left: 1, Right: 1\n * \"Sheet1!A1\" ==> Top 0, Bottom 0, Left: 0, Right: 0\n * \"Sheet1!B1:B3\" ==> Top 0, Bottom 3, Left: 1, Right: 1\n *\n * @param xc the string reference to convert\n *\n */\n function toZone(xc) {\n const zone = toUnboundedZone(xc);\n if (zone.bottom === undefined || zone.right === undefined) {\n throw new Error(\"This does not support unbounded ranges\");\n }\n return zone;\n }\n /**\n * Check that the zone has valid coordinates and in\n * the correct order.\n */\n function isZoneValid(zone) {\n // Typescript *should* prevent this kind of errors but\n // it's better to be on the safe side at runtime as well.\n const { bottom, top, left, right } = zone;\n if ((bottom !== undefined && isNaN(bottom)) ||\n isNaN(top) ||\n isNaN(left) ||\n (right !== undefined && isNaN(right))) {\n return false;\n }\n return ((zone.bottom === undefined || (zone.bottom >= zone.top && zone.bottom >= 0)) &&\n (zone.right === undefined || (zone.right >= zone.left && zone.right >= 0)) &&\n zone.top >= 0 &&\n zone.left >= 0);\n }\n /**\n * Convert from zone to a cartesian reference\n *\n */\n function zoneToXc(zone) {\n const { top, bottom, left, right } = zone;\n const hasHeader = \"hasHeader\" in zone ? zone.hasHeader : false;\n const isOneCell = top === bottom && left === right;\n if (bottom === undefined && right !== undefined) {\n return top === 0 && !hasHeader\n ? `${numberToLetters(left)}:${numberToLetters(right)}`\n : `${toXC(left, top)}:${numberToLetters(right)}`;\n }\n else if (right === undefined && bottom !== undefined) {\n return left === 0 && !hasHeader\n ? `${top + 1}:${bottom + 1}`\n : `${toXC(left, top)}:${bottom + 1}`;\n }\n else if (bottom !== undefined && right !== undefined) {\n return isOneCell ? toXC(left, top) : `${toXC(left, top)}:${toXC(right, bottom)}`;\n }\n throw new Error(_lt(\"Bad zone format\"));\n }\n /**\n * Expand a zone after inserting columns or rows.\n *\n * Don't resize the zone if a col/row was added right before/after the row but only move the zone.\n */\n function expandZoneOnInsertion(zone, start, base, position, quantity) {\n const dimension = start === \"left\" ? \"columns\" : \"rows\";\n const baseElement = position === \"before\" ? base - 1 : base;\n const end = start === \"left\" ? \"right\" : \"bottom\";\n const zoneEnd = zone[end];\n if (zone[start] <= baseElement && zoneEnd && zoneEnd > baseElement) {\n return createAdaptedZone(zone, dimension, \"RESIZE\", quantity);\n }\n if (baseElement < zone[start]) {\n return createAdaptedZone(zone, dimension, \"MOVE\", quantity);\n }\n return { ...zone };\n }\n /**\n * Update the selection after column/row addition\n */\n function updateSelectionOnInsertion(selection, start, base, position, quantity) {\n const dimension = start === \"left\" ? \"columns\" : \"rows\";\n const baseElement = position === \"before\" ? base - 1 : base;\n const end = start === \"left\" ? \"right\" : \"bottom\";\n if (selection[start] <= baseElement && selection[end] > baseElement) {\n return createAdaptedZone(selection, dimension, \"RESIZE\", quantity);\n }\n if (baseElement < selection[start]) {\n return createAdaptedZone(selection, dimension, \"MOVE\", quantity);\n }\n return { ...selection };\n }\n /**\n * Update the selection after column/row deletion\n */\n function updateSelectionOnDeletion(zone, start, elements) {\n const end = start === \"left\" ? \"right\" : \"bottom\";\n let newStart = zone[start];\n let newEnd = zone[end];\n for (let removedElement of elements.sort((a, b) => b - a)) {\n if (zone[start] > removedElement) {\n newStart--;\n newEnd--;\n }\n if (zone[start] < removedElement && zone[end] >= removedElement) {\n newEnd--;\n }\n }\n return { ...zone, [start]: newStart, [end]: newEnd };\n }\n /**\n * Reduce a zone after deletion of elements\n */\n function reduceZoneOnDeletion(zone, start, elements) {\n const end = start === \"left\" ? \"right\" : \"bottom\";\n let newStart = zone[start];\n let newEnd = zone[end];\n const zoneEnd = zone[end];\n for (let removedElement of elements.sort((a, b) => b - a)) {\n if (zone[start] > removedElement) {\n newStart--;\n if (newEnd !== undefined)\n newEnd--;\n }\n if (zoneEnd !== undefined &&\n newEnd !== undefined &&\n zone[start] <= removedElement &&\n zoneEnd >= removedElement) {\n newEnd--;\n }\n }\n if (newEnd !== undefined && newStart > newEnd) {\n return undefined;\n }\n return { ...zone, [start]: newStart, [end]: newEnd };\n }\n /**\n * Compute the union of multiple zones.\n */\n function union(...zones) {\n return {\n top: Math.min(...zones.map((zone) => zone.top)),\n left: Math.min(...zones.map((zone) => zone.left)),\n bottom: Math.max(...zones.map((zone) => zone.bottom)),\n right: Math.max(...zones.map((zone) => zone.right)),\n };\n }\n /**\n * Compute the intersection of two zones. Returns nothing if the two zones don't overlap\n */\n function intersection(z1, z2) {\n if (!overlap(z1, z2)) {\n return undefined;\n }\n return {\n top: Math.max(z1.top, z2.top),\n left: Math.max(z1.left, z2.left),\n bottom: Math.min(z1.bottom, z2.bottom),\n right: Math.min(z1.right, z2.right),\n };\n }\n /**\n * Two zones are equal if they represent the same area, so we clearly cannot use\n * reference equality.\n */\n function isEqual(z1, z2) {\n return (z1.left === z2.left && z1.right === z2.right && z1.top === z2.top && z1.bottom === z2.bottom);\n }\n /**\n * Return true if two zones overlap, false otherwise.\n */\n function overlap(z1, z2) {\n if (z1.bottom < z2.top || z2.bottom < z1.top) {\n return false;\n }\n if (z1.right < z2.left || z2.right < z1.left) {\n return false;\n }\n return true;\n }\n function isInside(col, row, zone) {\n const { left, right, top, bottom } = zone;\n return col >= left && col <= right && row >= top && row <= bottom;\n }\n /**\n * Check if a zone is inside another\n */\n function isZoneInside(smallZone, biggerZone) {\n return isEqual(union(biggerZone, smallZone), biggerZone);\n }\n /**\n * Recompute the ranges of the zone to contain all the cells in zones, without the cells in toRemoveZones\n * Also regroup zones together to shorten the string\n */\n function recomputeZones(zonesXc, toRemoveZonesXc) {\n const zones = zonesXc.map(toUnboundedZone);\n const zonesToRemove = toRemoveZonesXc.map(toZone);\n // Compute the max to replace the bottom of full columns and right of full rows by something\n // bigger than any other col/row to be able to apply the algorithm while keeping tracks of what\n // zones are full cols/rows\n const maxBottom = Math.max(...zones.concat(zonesToRemove).map((zone) => zone.bottom || 0));\n const maxRight = Math.max(...zones.concat(zonesToRemove).map((zone) => zone.right || 0));\n const expandedZones = zones.map((zone) => ({\n ...zone,\n bottom: zone.bottom === undefined ? maxBottom + 1 : zone.bottom,\n right: zone.right === undefined ? maxRight + 1 : zone.right,\n }));\n const expandedZonesToRemove = zonesToRemove.map((zone) => ({\n ...zone,\n bottom: zone.bottom === undefined ? maxBottom + 1 : zone.bottom,\n right: zone.right === undefined ? maxRight + 1 : zone.right,\n }));\n const zonePositions = expandedZones.map(positions).flat();\n const positionsToRemove = expandedZonesToRemove.map(positions).flat();\n const positionToKeep = positionsDifference(zonePositions, positionsToRemove);\n const columns = mergePositionsIntoColumns(positionToKeep);\n return mergeAlignedColumns(columns)\n .map((zone) => ({\n ...zone,\n bottom: zone.bottom === maxBottom + 1 ? undefined : zone.bottom,\n right: zone.right === maxRight + 1 ? undefined : zone.right,\n }))\n .map(zoneToXc);\n }\n /**\n * Merge aligned adjacent columns into single zones\n * e.g. A1:A5 and B1:B5 are merged into A1:B5\n */\n function mergeAlignedColumns(columns) {\n if (columns.length === 0) {\n return [];\n }\n if (columns.some((zone) => zone.left !== zone.right)) {\n throw new Error(\"only columns can be merged\");\n }\n const done = [];\n const cols = removeRedundantZones(columns);\n const isAdjacentAndAligned = (zone, nextZone) => zone.top === nextZone.top &&\n zone.bottom === nextZone.bottom &&\n zone.right + 1 === nextZone.left;\n while (cols.length) {\n const merged = cols.reduce((zone, nextZone) => (isAdjacentAndAligned(zone, nextZone) ? union(zone, nextZone) : zone), cols.shift());\n done.push(merged);\n }\n return removeRedundantZones(done);\n }\n /**\n * Remove redundant zones in the list.\n * i.e. zones included in another zone.\n */\n function removeRedundantZones(zones) {\n const sortedZones = [...zones]\n .sort((a, b) => b.right - a.right)\n .sort((a, b) => b.bottom - a.bottom)\n .sort((a, b) => a.top - b.top)\n .sort((a, b) => a.left - b.left)\n .reverse();\n const checked = [];\n while (sortedZones.length !== 0) {\n const zone = sortedZones.shift();\n const isIncludedInOther = sortedZones.some((otherZone) => isZoneInside(zone, otherZone));\n if (!isIncludedInOther) {\n checked.push(zone);\n }\n }\n return checked.reverse();\n }\n /**\n * Merge adjacent positions into vertical zones (columns)\n */\n function mergePositionsIntoColumns(positions) {\n if (positions.length === 0) {\n return [];\n }\n const [startingPosition, ...sortedPositions] = [...positions]\n .sort((a, b) => a.row - b.row)\n .sort((a, b) => a.col - b.col);\n const done = [];\n let active = positionToZone(startingPosition);\n for (const { col, row } of sortedPositions) {\n if (isInside(col, row, active)) {\n continue;\n }\n else if (col === active.left && row === active.bottom + 1) {\n const bottom = active.bottom + 1;\n active = { ...active, bottom };\n }\n else {\n done.push(active);\n active = positionToZone({ col, row });\n }\n }\n return [...done, active];\n }\n /**\n * Returns positions in the first array which are not in the second array.\n */\n function positionsDifference(positions, toRemove) {\n const forbidden = new Set(toRemove.map(({ col, row }) => `${col}-${row}`));\n return positions.filter(({ col, row }) => !forbidden.has(`${col}-${row}`));\n }\n function zoneToDimension(zone) {\n return {\n height: zone.bottom - zone.top + 1,\n width: zone.right - zone.left + 1,\n };\n }\n function isOneDimensional(zone) {\n const { width, height } = zoneToDimension(zone);\n return width === 1 || height === 1;\n }\n /**\n * Array of all positions in the zone.\n */\n function positions(zone) {\n const positions = [];\n const [left, right] = [zone.right, zone.left].sort((a, b) => a - b);\n const [top, bottom] = [zone.top, zone.bottom].sort((a, b) => a - b);\n for (const col of range(left, right + 1)) {\n for (const row of range(top, bottom + 1)) {\n positions.push({ col, row });\n }\n }\n return positions;\n }\n /**\n * This function returns a zone with coordinates modified according to the change\n * applied to the zone. It may be possible to change the zone by resizing or moving\n * it according to different dimensions.\n *\n * @param zone the zone to modify\n * @param dimension the direction to change the zone among \"columns\", \"rows\" and\n * \"both\"\n * @param operation how to change the zone, modify its size \"RESIZE\" or modify\n * its location \"MOVE\"\n * @param by a number of how many units the change should be made. This parameter\n * takes the form of a two-number array when the dimension is \"both\"\n */\n function createAdaptedZone(zone, dimension, operation, by) {\n const offsetX = dimension === \"both\" ? by[0] : dimension === \"columns\" ? by : 0;\n const offsetY = dimension === \"both\" ? by[1] : dimension === \"rows\" ? by : 0;\n // For full columns/rows, we have to make the distinction between the one that have a header and\n // whose start should be moved (eg. A2:A), and those who don't (eg. A:A)\n // The only time we don't want to move the start of the zone is if the zone is a full column (or a full row)\n // without header and that we are adding/removing a row (or a column)\n const hasHeader = \"hasHeader\" in zone ? zone.hasHeader : false;\n let shouldStartBeMoved;\n if (isFullCol(zone) && !hasHeader) {\n shouldStartBeMoved = dimension !== \"rows\";\n }\n else if (isFullRow(zone) && !hasHeader) {\n shouldStartBeMoved = dimension !== \"columns\";\n }\n else {\n shouldStartBeMoved = true;\n }\n const newZone = { ...zone };\n if (shouldStartBeMoved && operation === \"MOVE\") {\n newZone[\"left\"] += offsetX;\n newZone[\"top\"] += offsetY;\n }\n if (newZone[\"right\"] !== undefined) {\n newZone[\"right\"] += offsetX;\n }\n if (newZone[\"bottom\"] !== undefined) {\n newZone[\"bottom\"] += offsetY;\n }\n return newZone;\n }\n /**\n * Returns a Zone array with unique occurrence of each zone.\n * For each multiple occurrence, the occurrence with the largest index is kept.\n * This allows to always have the last selection made in the last position.\n * */\n function uniqueZones(zones) {\n return zones\n .reverse()\n .filter((zone, index, self) => index ===\n self.findIndex((z) => z.top === zone.top &&\n z.bottom === zone.bottom &&\n z.left === zone.left &&\n z.right === zone.right))\n .reverse();\n }\n /**\n * This function will find all overlapping zones in an array and transform them\n * into an union of each one.\n * */\n function mergeOverlappingZones(zones) {\n return zones.reduce((dissociatedZones, zone) => {\n const nextIndex = dissociatedZones.length;\n for (let i = 0; i < nextIndex; i++) {\n if (overlap(dissociatedZones[i], zone)) {\n dissociatedZones[i] = union(dissociatedZones[i], zone);\n return dissociatedZones;\n }\n }\n dissociatedZones[nextIndex] = zone;\n return dissociatedZones;\n }, []);\n }\n /**\n * This function will compare the modifications of selection to determine\n * a cell that is part of the new zone and not the previous one.\n */\n function findCellInNewZone(oldZone, currentZone) {\n let col, row;\n const { left: oldLeft, right: oldRight, top: oldTop, bottom: oldBottom } = oldZone;\n const { left, right, top, bottom } = currentZone;\n if (left != oldLeft) {\n col = left;\n }\n else if (right != oldRight) {\n col = right;\n }\n else {\n // left and right don't change\n col = left;\n }\n if (top != oldTop) {\n row = top;\n }\n else if (bottom != oldBottom) {\n row = bottom;\n }\n else {\n // top and bottom don't change\n row = top;\n }\n return { col, row };\n }\n function organizeZone(zone) {\n return {\n top: Math.min(zone.top, zone.bottom),\n bottom: Math.max(zone.top, zone.bottom),\n left: Math.min(zone.left, zone.right),\n right: Math.max(zone.left, zone.right),\n };\n }\n function positionToZone(position) {\n return { left: position.col, right: position.col, top: position.row, bottom: position.row };\n }\n function isFullRow(zone) {\n return zone.right === undefined;\n }\n function isFullCol(zone) {\n return zone.bottom === undefined;\n }\n /** Returns the area of a zone */\n function getZoneArea(zone) {\n return (zone.bottom - zone.top + 1) * (zone.right - zone.left + 1);\n }\n /**\n * Check if the zones are continuous, ie. if they can be merged into a single zone without\n * including cells outside the zones\n * */\n function areZonesContinuous(...zones) {\n if (zones.length < 2)\n return true;\n return recomputeZones(zones.map(zoneToXc), []).length === 1;\n }\n /** Return all the columns in the given list of zones */\n function getZonesCols(zones) {\n const set = new Set();\n for (let zone of zones) {\n for (let col of range(zone.left, zone.right + 1)) {\n set.add(col);\n }\n }\n return set;\n }\n /** Return all the rows in the given list of zones */\n function getZonesRows(zones) {\n const set = new Set();\n for (let zone of zones) {\n for (let row of range(zone.top, zone.bottom + 1)) {\n set.add(row);\n }\n }\n return set;\n }\n\n class ChartJsComponent extends owl.Component {\n constructor() {\n super(...arguments);\n this.canvas = owl.useRef(\"graphContainer\");\n }\n get background() {\n return this.chartRuntime.background;\n }\n get canvasStyle() {\n return `background-color: ${this.background}`;\n }\n get chartRuntime() {\n const runtime = this.env.model.getters.getChartRuntime(this.props.figure.id);\n if (!(\"chartJsConfig\" in runtime)) {\n throw new Error(\"Unsupported chart runtime\");\n }\n return runtime;\n }\n setup() {\n owl.onMounted(() => {\n const runtime = this.chartRuntime;\n this.createChart(runtime.chartJsConfig);\n });\n let previousRuntime = this.chartRuntime;\n owl.onPatched(() => {\n const chartRuntime = this.chartRuntime;\n if (deepEquals(previousRuntime, chartRuntime)) {\n return;\n }\n this.updateChartJs(chartRuntime);\n previousRuntime = chartRuntime;\n });\n }\n createChart(chartData) {\n const canvas = this.canvas.el;\n const ctx = canvas.getContext(\"2d\");\n this.chart = new window.Chart(ctx, chartData);\n }\n updateChartJs(chartRuntime) {\n var _a, _b, _c, _d;\n const chartData = chartRuntime.chartJsConfig;\n if (chartData.data && chartData.data.datasets) {\n this.chart.data = chartData.data;\n if ((_a = chartData.options) === null || _a === void 0 ? void 0 : _a.title) {\n this.chart.config.options.title = chartData.options.title;\n }\n if (chartData.options && \"valueLabel\" in chartData.options) {\n if ((_b = chartData.options) === null || _b === void 0 ? void 0 : _b.valueLabel) {\n this.chart.config.options.valueLabel =\n chartData.options.valueLabel;\n }\n }\n }\n else {\n this.chart.data.datasets = undefined;\n }\n this.chart.config.options.legend = (_c = chartData.options) === null || _c === void 0 ? void 0 : _c.legend;\n this.chart.config.options.scales = (_d = chartData.options) === null || _d === void 0 ? void 0 : _d.scales;\n this.chart.update({ duration: 0 });\n }\n }\n ChartJsComponent.template = \"o-spreadsheet-ChartJsComponent\";\n ChartJsComponent.props = {\n figure: Object,\n };\n\n /**\n * This file is largely inspired by owl 1.\n * `css` tag has been removed from owl 2 without workaround to manage css.\n * So, the solution was to import the behavior of owl 1 directly in our\n * codebase, with one difference: the css is added to the sheet as soon as the\n * css tag is executed. In owl 1, the css was added as soon as a Component was\n * created for the first time.\n */\n const STYLESHEETS = {};\n let nextId = 0;\n /**\n * CSS tag helper for defining inline stylesheets. With this, one can simply define\n * an inline stylesheet with just the following code:\n * ```js\n * css`.component-a { color: red; }`;\n * ```\n */\n function css(strings, ...args) {\n const name = `__sheet__${nextId++}`;\n const value = String.raw(strings, ...args);\n registerSheet(name, value);\n activateSheet(name);\n return name;\n }\n function processSheet(str) {\n const tokens = str.split(/(\\{|\\}|;)/).map((s) => s.trim());\n const selectorStack = [];\n const parts = [];\n let rules = [];\n function generateSelector(stackIndex, parentSelector) {\n const parts = [];\n for (const selector of selectorStack[stackIndex]) {\n let part = (parentSelector && parentSelector + \" \" + selector) || selector;\n if (part.includes(\"&\")) {\n part = selector.replace(/&/g, parentSelector || \"\");\n }\n if (stackIndex < selectorStack.length - 1) {\n part = generateSelector(stackIndex + 1, part);\n }\n parts.push(part);\n }\n return parts.join(\", \");\n }\n function generateRules() {\n if (rules.length) {\n parts.push(generateSelector(0) + \" {\");\n parts.push(...rules);\n parts.push(\"}\");\n rules = [];\n }\n }\n while (tokens.length) {\n let token = tokens.shift();\n if (token === \"}\") {\n generateRules();\n selectorStack.pop();\n }\n else {\n if (tokens[0] === \"{\") {\n generateRules();\n selectorStack.push(token.split(/\\s*,\\s*/));\n tokens.shift();\n }\n if (tokens[0] === \";\") {\n rules.push(\" \" + token + \";\");\n }\n }\n }\n return parts.join(\"\\n\");\n }\n function registerSheet(id, css) {\n const sheet = document.createElement(\"style\");\n sheet.textContent = processSheet(css);\n STYLESHEETS[id] = sheet;\n }\n function activateSheet(id) {\n const sheet = STYLESHEETS[id];\n sheet.setAttribute(\"component\", id);\n document.head.appendChild(sheet);\n }\n function getTextDecoration({ strikethrough, underline, }) {\n if (!strikethrough && !underline) {\n return \"none\";\n }\n return `${strikethrough ? \"line-through\" : \"\"} ${underline ? \"underline\" : \"\"}`;\n }\n /**\n * Convert the cell style to CSS properties.\n */\n function cellStyleToCss(style) {\n const attributes = cellTextStyleToCss(style);\n if (!style)\n return attributes;\n if (style.fillColor) {\n attributes[\"background\"] = style.fillColor;\n }\n return attributes;\n }\n /**\n * Convert the cell text style to CSS properties.\n */\n function cellTextStyleToCss(style) {\n const attributes = {};\n if (!style)\n return attributes;\n if (style.bold) {\n attributes[\"font-weight\"] = \"bold\";\n }\n if (style.italic) {\n attributes[\"font-style\"] = \"italic\";\n }\n if (style.strikethrough || style.underline) {\n let decoration = style.strikethrough ? \"line-through\" : \"\";\n decoration = style.underline ? decoration + \" underline\" : decoration;\n attributes[\"text-decoration\"] = decoration;\n }\n if (style.textColor) {\n attributes[\"color\"] = style.textColor;\n }\n return attributes;\n }\n function cssPropertiesToCss(attributes, newLine = true) {\n const separator = newLine ? \"\\n\" : \"\";\n const str = Object.entries(attributes)\n .map(([attName, attValue]) => `${attName}: ${attValue};`)\n .join(separator);\n return str ? \"\\n\" + str + \"\\n\" : \"\";\n }\n\n /* Sizes of boxes containing the texts, in percentage of the Chart size */\n const TITLE_FONT_SIZE = 18;\n const BASELINE_BOX_HEIGHT_RATIO = 0.35;\n const KEY_BOX_HEIGHT_RATIO = 0.65;\n /** Baseline description should have a smaller font than the baseline */\n const BASELINE_DESCR_FONT_RATIO = 0.9;\n /* Padding at the border of the chart, in percentage of the chart width */\n const CHART_PADDING_RATIO = 0.02;\n /**\n * Line height (in em)\n * Having a line heigh =1em (=font size) don't work, the font will overflow.\n */\n const LINE_HEIGHT = 1.2;\n css /* scss */ `\n div.o-scorecard {\n user-select: none;\n background-color: white;\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n\n .o-scorecard-content {\n display: flex;\n flex-direction: column;\n height: 100%;\n justify-content: center;\n text-align: center;\n }\n\n .o-title-text {\n text-align: left;\n height: ${LINE_HEIGHT + \"em\"};\n line-height: ${LINE_HEIGHT + \"em\"};\n overflow: hidden;\n white-space: nowrap;\n }\n\n .o-key-text {\n line-height: ${LINE_HEIGHT + \"em\"};\n height: ${LINE_HEIGHT + \"em\"};\n overflow: hidden;\n white-space: nowrap;\n }\n\n .o-cf-icon {\n display: inline-block;\n width: 0.65em;\n height: 1em;\n line-height: 1em;\n padding-bottom: 0.07em;\n padding-right: 3px;\n }\n\n .o-baseline-text {\n line-height: ${LINE_HEIGHT + \"em\"};\n height: ${LINE_HEIGHT + \"em\"};\n overflow: hidden;\n white-space: nowrap;\n\n .o-baseline-text-description {\n white-space: pre;\n }\n }\n }\n`;\n class ScorecardChart$1 extends owl.Component {\n constructor() {\n super(...arguments);\n this.ctx = document.createElement(\"canvas\").getContext(\"2d\");\n }\n get runtime() {\n return this.env.model.getters.getChartRuntime(this.props.figure.id);\n }\n get title() {\n return this.runtime.title;\n }\n get keyValue() {\n return this.runtime.keyValue;\n }\n get baseline() {\n return this.runtime.baselineDisplay;\n }\n get baselineDescr() {\n const baselineDescr = this.runtime.baselineDescr || \"\";\n return this.baseline && baselineDescr ? \" \" + baselineDescr : baselineDescr;\n }\n get baselineArrowDirection() {\n return this.runtime.baselineArrow;\n }\n get backgroundColor() {\n return this.runtime.background;\n }\n get primaryFontColor() {\n return this.runtime.fontColor;\n }\n get secondaryFontColor() {\n return relativeLuminance(this.backgroundColor) > 0.3 ? \"#525252\" : \"#C8C8C8\";\n }\n get figure() {\n return this.props.figure;\n }\n get chartStyle() {\n return `\n padding:${this.chartPadding}px;\n background:${this.backgroundColor};\n `;\n }\n get chartContentStyle() {\n return `\n height:${this.getDrawableHeight()}px;\n `;\n }\n get chartPadding() {\n return this.props.figure.width * CHART_PADDING_RATIO;\n }\n getTextStyles() {\n // If the widest text overflows horizontally, scale it down, and apply the same scaling factors to all the other fonts.\n const maxLineWidth = this.props.figure.width * (1 - 2 * CHART_PADDING_RATIO);\n const widestElement = this.getWidestElement();\n const baseFontSize = widestElement.getElementMaxFontSize(this.getDrawableHeight(), this);\n const fontSizeMatchingWidth = getFontSizeMatchingWidth(maxLineWidth, baseFontSize, (fontSize) => widestElement.getElementWidth(fontSize, this.ctx, this));\n let scalingFactor = fontSizeMatchingWidth / baseFontSize;\n // Fonts sizes in px\n const keyFontSize = new KeyValueElement().getElementMaxFontSize(this.getDrawableHeight(), this) * scalingFactor;\n const baselineFontSize = new BaselineElement().getElementMaxFontSize(this.getDrawableHeight(), this) * scalingFactor;\n return {\n titleStyle: this.getTextStyle({\n fontSize: TITLE_FONT_SIZE,\n color: this.secondaryFontColor,\n }),\n keyStyle: this.getTextStyle({\n fontSize: keyFontSize,\n cellStyle: this.runtime.keyValueStyle,\n color: this.primaryFontColor,\n }),\n baselineStyle: this.getTextStyle({\n fontSize: baselineFontSize,\n }),\n baselineValueStyle: this.getTextStyle({\n fontSize: baselineFontSize,\n cellStyle: this.runtime.baselineStyle,\n color: this.runtime.baselineColor || this.secondaryFontColor,\n }),\n baselineDescrStyle: this.getTextStyle({\n fontSize: baselineFontSize * BASELINE_DESCR_FONT_RATIO,\n color: this.secondaryFontColor,\n }),\n };\n }\n /** Return an CSS style string corresponding to the given arguments */\n getTextStyle(args) {\n const cssAttributes = cellTextStyleToCss(args.cellStyle);\n cssAttributes[\"font-size\"] = `${args.fontSize}px`;\n cssAttributes[\"display\"] = \"inline-block\";\n if (!cssAttributes[\"color\"] && args.color) {\n cssAttributes[\"color\"] = args.color;\n }\n return cssPropertiesToCss(cssAttributes);\n }\n /** Get the height of the chart minus all the vertical paddings */\n getDrawableHeight() {\n const verticalPadding = 2 * this.chartPadding;\n let availableHeight = this.props.figure.height - verticalPadding;\n availableHeight -= this.title ? TITLE_FONT_SIZE * LINE_HEIGHT : 0;\n return availableHeight;\n }\n /** Return the element with he widest text in the chart */\n getWidestElement() {\n const baseline = new BaselineElement();\n const keyValue = new KeyValueElement();\n return baseline.getElementWidth(BASELINE_BOX_HEIGHT_RATIO, this.ctx, this) >\n keyValue.getElementWidth(KEY_BOX_HEIGHT_RATIO, this.ctx, this)\n ? baseline\n : keyValue;\n }\n }\n ScorecardChart$1.template = \"o-spreadsheet-ScorecardChart\";\n class BaselineElement {\n getElementWidth(fontSize, ctx, chart) {\n if (!chart.runtime)\n return 0;\n const baselineStr = chart.baseline;\n // Put mock text to simulate the width of the up/down arrow\n const largeText = chart.baselineArrowDirection !== \"neutral\" ? \"A \" + baselineStr : baselineStr;\n ctx.font = `${fontSize}px ${DEFAULT_FONT}`;\n let textWidth = ctx.measureText(largeText).width;\n // Baseline descr font size should be smaller than baseline font size\n ctx.font = `${fontSize * BASELINE_DESCR_FONT_RATIO}px ${DEFAULT_FONT}`;\n textWidth += ctx.measureText(chart.baselineDescr).width;\n return textWidth;\n }\n getElementMaxFontSize(availableHeight, chart) {\n if (!chart.runtime)\n return 0;\n const haveBaseline = chart.baseline !== \"\" || chart.baselineDescr;\n const maxHeight = haveBaseline ? BASELINE_BOX_HEIGHT_RATIO * availableHeight : 0;\n return maxHeight / LINE_HEIGHT;\n }\n }\n class KeyValueElement {\n getElementWidth(fontSize, ctx, chart) {\n if (!chart.runtime)\n return 0;\n const str = chart.keyValue || \"\";\n ctx.font = `${fontSize}px ${DEFAULT_FONT}`;\n return ctx.measureText(str).width;\n }\n getElementMaxFontSize(availableHeight, chart) {\n if (!chart.runtime)\n return 0;\n const haveBaseline = chart.baseline !== \"\" || chart.baselineDescr;\n const maxHeight = haveBaseline ? KEY_BOX_HEIGHT_RATIO * availableHeight : availableHeight;\n return maxHeight / LINE_HEIGHT;\n }\n }\n ScorecardChart$1.props = {\n figure: Object,\n };\n\n /**\n * Convert a JS color hexadecimal to an excel compatible color.\n *\n * In Excel the color don't start with a '#' and the format is AARRGGBB instead of RRGGBBAA\n */\n function toXlsxHexColor(color) {\n color = toHex(color).replace(\"#\", \"\");\n // alpha channel goes first\n if (color.length === 8) {\n return color.slice(6) + color.slice(0, 6);\n }\n return color;\n }\n\n /**\n * AbstractChart is the class from which every Chart should inherit.\n * The role of this class is to maintain the state of each chart.\n */\n class AbstractChart {\n constructor(definition, sheetId, getters) {\n this.title = definition.title;\n this.sheetId = sheetId;\n this.getters = getters;\n }\n /**\n * Validate the chart definition given as arguments. This function will be\n * called from allowDispatch function\n */\n static validateChartDefinition(validator, definition) {\n throw new Error(\"This method should be implemented by sub class\");\n }\n /**\n * Get a new chart definition transformed with the executed command. This\n * functions will be called during operational transform process\n */\n static transformDefinition(definition, executed) {\n throw new Error(\"This method should be implemented by sub class\");\n }\n /**\n * Get an empty definition based on the given context\n */\n static getDefinitionFromContextCreation(context) {\n throw new Error(\"This method should be implemented by sub class\");\n }\n }\n\n function transformZone(zone, executed) {\n if (executed.type === \"REMOVE_COLUMNS_ROWS\") {\n return reduceZoneOnDeletion(zone, executed.dimension === \"COL\" ? \"left\" : \"top\", executed.elements);\n }\n if (executed.type === \"ADD_COLUMNS_ROWS\") {\n return expandZoneOnInsertion(zone, executed.dimension === \"COL\" ? \"left\" : \"top\", executed.base, executed.position, executed.quantity);\n }\n return { ...zone };\n }\n\n var CellValueType;\n (function (CellValueType) {\n CellValueType[\"boolean\"] = \"boolean\";\n CellValueType[\"number\"] = \"number\";\n CellValueType[\"text\"] = \"text\";\n CellValueType[\"empty\"] = \"empty\";\n CellValueType[\"error\"] = \"error\";\n })(CellValueType || (CellValueType = {}));\n\n var ClipboardMIMEType;\n (function (ClipboardMIMEType) {\n ClipboardMIMEType[\"PlainText\"] = \"text/plain\";\n ClipboardMIMEType[\"Html\"] = \"text/html\";\n })(ClipboardMIMEType || (ClipboardMIMEType = {}));\n\n function isSheetDependent(cmd) {\n return \"sheetId\" in cmd;\n }\n function isGridDependent(cmd) {\n return \"dimension\" in cmd && \"sheetId\" in cmd;\n }\n function isTargetDependent(cmd) {\n return \"target\" in cmd && \"sheetId\" in cmd;\n }\n function isRangeDependant(cmd) {\n return \"ranges\" in cmd;\n }\n function isPositionDependent(cmd) {\n return \"col\" in cmd && \"row\" in cmd && \"sheetId\" in cmd;\n }\n const invalidateEvaluationCommands = new Set([\n \"RENAME_SHEET\",\n \"DELETE_SHEET\",\n \"CREATE_SHEET\",\n \"ADD_COLUMNS_ROWS\",\n \"REMOVE_COLUMNS_ROWS\",\n \"DELETE_CELL\",\n \"INSERT_CELL\",\n \"UNDO\",\n \"REDO\",\n ]);\n const invalidateCFEvaluationCommands = new Set([\n ...invalidateEvaluationCommands,\n \"DUPLICATE_SHEET\",\n \"EVALUATE_CELLS\",\n \"ADD_CONDITIONAL_FORMAT\",\n \"REMOVE_CONDITIONAL_FORMAT\",\n \"MOVE_CONDITIONAL_FORMAT\",\n ]);\n const readonlyAllowedCommands = new Set([\n \"START\",\n \"ACTIVATE_SHEET\",\n \"COPY\",\n \"PREPARE_SELECTION_INPUT_EXPANSION\",\n \"STOP_SELECTION_INPUT\",\n \"RESIZE_SHEETVIEW\",\n \"SET_VIEWPORT_OFFSET\",\n \"SELECT_SEARCH_NEXT_MATCH\",\n \"SELECT_SEARCH_PREVIOUS_MATCH\",\n \"REFRESH_SEARCH\",\n \"UPDATE_SEARCH\",\n \"CLEAR_SEARCH\",\n \"EVALUATE_CELLS\",\n \"SET_CURRENT_CONTENT\",\n \"SET_FORMULA_VISIBILITY\",\n \"OPEN_CELL_POPOVER\",\n \"CLOSE_CELL_POPOVER\",\n \"UPDATE_FILTER\",\n ]);\n const coreTypes = new Set([\n /** CELLS */\n \"UPDATE_CELL\",\n \"UPDATE_CELL_POSITION\",\n \"CLEAR_CELL\",\n \"DELETE_CONTENT\",\n /** GRID SHAPE */\n \"ADD_COLUMNS_ROWS\",\n \"REMOVE_COLUMNS_ROWS\",\n \"RESIZE_COLUMNS_ROWS\",\n \"HIDE_COLUMNS_ROWS\",\n \"UNHIDE_COLUMNS_ROWS\",\n \"SET_GRID_LINES_VISIBILITY\",\n \"UNFREEZE_COLUMNS\",\n \"UNFREEZE_ROWS\",\n \"FREEZE_COLUMNS\",\n \"FREEZE_ROWS\",\n \"UNFREEZE_COLUMNS_ROWS\",\n /** MERGE */\n \"ADD_MERGE\",\n \"REMOVE_MERGE\",\n /** SHEETS MANIPULATION */\n \"CREATE_SHEET\",\n \"DELETE_SHEET\",\n \"DUPLICATE_SHEET\",\n \"MOVE_SHEET\",\n \"RENAME_SHEET\",\n \"HIDE_SHEET\",\n \"SHOW_SHEET\",\n /** RANGES MANIPULATION */\n \"MOVE_RANGES\",\n /** CONDITIONAL FORMAT */\n \"ADD_CONDITIONAL_FORMAT\",\n \"REMOVE_CONDITIONAL_FORMAT\",\n \"MOVE_CONDITIONAL_FORMAT\",\n /** FIGURES */\n \"CREATE_FIGURE\",\n \"DELETE_FIGURE\",\n \"UPDATE_FIGURE\",\n /** FORMATTING */\n \"SET_FORMATTING\",\n \"CLEAR_FORMATTING\",\n \"SET_BORDER\",\n /** CHART */\n \"CREATE_CHART\",\n \"UPDATE_CHART\",\n /** FILTERS */\n \"CREATE_FILTER_TABLE\",\n \"REMOVE_FILTER_TABLE\",\n /** IMAGE */\n \"CREATE_IMAGE\",\n ]);\n function isCoreCommand(cmd) {\n return coreTypes.has(cmd.type);\n }\n function canExecuteInReadonly(cmd) {\n return readonlyAllowedCommands.has(cmd.type);\n }\n /**\n * Holds the result of a command dispatch.\n * The command may have been successfully dispatched or cancelled\n * for one or more reasons.\n */\n class DispatchResult {\n constructor(results = []) {\n if (!Array.isArray(results)) {\n results = [results];\n }\n results = [...new Set(results)];\n this.reasons = results.filter((result) => result !== 0 /* CommandResult.Success */);\n }\n /**\n * Static helper which returns a successful DispatchResult\n */\n static get Success() {\n return new DispatchResult();\n }\n get isSuccessful() {\n return this.reasons.length === 0;\n }\n /**\n * Check if the dispatch has been cancelled because of\n * the given reason.\n */\n isCancelledBecause(reason) {\n return this.reasons.includes(reason);\n }\n }\n exports.CommandResult = void 0;\n (function (CommandResult) {\n CommandResult[CommandResult[\"Success\"] = 0] = \"Success\";\n CommandResult[CommandResult[\"CancelledForUnknownReason\"] = 1] = \"CancelledForUnknownReason\";\n CommandResult[CommandResult[\"WillRemoveExistingMerge\"] = 2] = \"WillRemoveExistingMerge\";\n CommandResult[CommandResult[\"MergeIsDestructive\"] = 3] = \"MergeIsDestructive\";\n CommandResult[CommandResult[\"CellIsMerged\"] = 4] = \"CellIsMerged\";\n CommandResult[CommandResult[\"InvalidTarget\"] = 5] = \"InvalidTarget\";\n CommandResult[CommandResult[\"EmptyUndoStack\"] = 6] = \"EmptyUndoStack\";\n CommandResult[CommandResult[\"EmptyRedoStack\"] = 7] = \"EmptyRedoStack\";\n CommandResult[CommandResult[\"NotEnoughElements\"] = 8] = \"NotEnoughElements\";\n CommandResult[CommandResult[\"NotEnoughSheets\"] = 9] = \"NotEnoughSheets\";\n CommandResult[CommandResult[\"MissingSheetName\"] = 10] = \"MissingSheetName\";\n CommandResult[CommandResult[\"DuplicatedSheetName\"] = 11] = \"DuplicatedSheetName\";\n CommandResult[CommandResult[\"DuplicatedSheetId\"] = 12] = \"DuplicatedSheetId\";\n CommandResult[CommandResult[\"ForbiddenCharactersInSheetName\"] = 13] = \"ForbiddenCharactersInSheetName\";\n CommandResult[CommandResult[\"WrongSheetMove\"] = 14] = \"WrongSheetMove\";\n CommandResult[CommandResult[\"WrongSheetPosition\"] = 15] = \"WrongSheetPosition\";\n CommandResult[CommandResult[\"InvalidAnchorZone\"] = 16] = \"InvalidAnchorZone\";\n CommandResult[CommandResult[\"SelectionOutOfBound\"] = 17] = \"SelectionOutOfBound\";\n CommandResult[CommandResult[\"TargetOutOfSheet\"] = 18] = \"TargetOutOfSheet\";\n CommandResult[CommandResult[\"WrongCutSelection\"] = 19] = \"WrongCutSelection\";\n CommandResult[CommandResult[\"WrongPasteSelection\"] = 20] = \"WrongPasteSelection\";\n CommandResult[CommandResult[\"WrongPasteOption\"] = 21] = \"WrongPasteOption\";\n CommandResult[CommandResult[\"WrongFigurePasteOption\"] = 22] = \"WrongFigurePasteOption\";\n CommandResult[CommandResult[\"EmptyClipboard\"] = 23] = \"EmptyClipboard\";\n CommandResult[CommandResult[\"EmptyRange\"] = 24] = \"EmptyRange\";\n CommandResult[CommandResult[\"InvalidRange\"] = 25] = \"InvalidRange\";\n CommandResult[CommandResult[\"InvalidZones\"] = 26] = \"InvalidZones\";\n CommandResult[CommandResult[\"InvalidSheetId\"] = 27] = \"InvalidSheetId\";\n CommandResult[CommandResult[\"InvalidFigureId\"] = 28] = \"InvalidFigureId\";\n CommandResult[CommandResult[\"InputAlreadyFocused\"] = 29] = \"InputAlreadyFocused\";\n CommandResult[CommandResult[\"MaximumRangesReached\"] = 30] = \"MaximumRangesReached\";\n CommandResult[CommandResult[\"InvalidChartDefinition\"] = 31] = \"InvalidChartDefinition\";\n CommandResult[CommandResult[\"InvalidDataSet\"] = 32] = \"InvalidDataSet\";\n CommandResult[CommandResult[\"InvalidLabelRange\"] = 33] = \"InvalidLabelRange\";\n CommandResult[CommandResult[\"InvalidScorecardKeyValue\"] = 34] = \"InvalidScorecardKeyValue\";\n CommandResult[CommandResult[\"InvalidScorecardBaseline\"] = 35] = \"InvalidScorecardBaseline\";\n CommandResult[CommandResult[\"InvalidGaugeDataRange\"] = 36] = \"InvalidGaugeDataRange\";\n CommandResult[CommandResult[\"EmptyGaugeRangeMin\"] = 37] = \"EmptyGaugeRangeMin\";\n CommandResult[CommandResult[\"GaugeRangeMinNaN\"] = 38] = \"GaugeRangeMinNaN\";\n CommandResult[CommandResult[\"EmptyGaugeRangeMax\"] = 39] = \"EmptyGaugeRangeMax\";\n CommandResult[CommandResult[\"GaugeRangeMaxNaN\"] = 40] = \"GaugeRangeMaxNaN\";\n CommandResult[CommandResult[\"GaugeRangeMinBiggerThanRangeMax\"] = 41] = \"GaugeRangeMinBiggerThanRangeMax\";\n CommandResult[CommandResult[\"GaugeLowerInflectionPointNaN\"] = 42] = \"GaugeLowerInflectionPointNaN\";\n CommandResult[CommandResult[\"GaugeUpperInflectionPointNaN\"] = 43] = \"GaugeUpperInflectionPointNaN\";\n CommandResult[CommandResult[\"GaugeLowerBiggerThanUpper\"] = 44] = \"GaugeLowerBiggerThanUpper\";\n CommandResult[CommandResult[\"InvalidAutofillSelection\"] = 45] = \"InvalidAutofillSelection\";\n CommandResult[CommandResult[\"WrongComposerSelection\"] = 46] = \"WrongComposerSelection\";\n CommandResult[CommandResult[\"MinBiggerThanMax\"] = 47] = \"MinBiggerThanMax\";\n CommandResult[CommandResult[\"LowerBiggerThanUpper\"] = 48] = \"LowerBiggerThanUpper\";\n CommandResult[CommandResult[\"MidBiggerThanMax\"] = 49] = \"MidBiggerThanMax\";\n CommandResult[CommandResult[\"MinBiggerThanMid\"] = 50] = \"MinBiggerThanMid\";\n CommandResult[CommandResult[\"FirstArgMissing\"] = 51] = \"FirstArgMissing\";\n CommandResult[CommandResult[\"SecondArgMissing\"] = 52] = \"SecondArgMissing\";\n CommandResult[CommandResult[\"MinNaN\"] = 53] = \"MinNaN\";\n CommandResult[CommandResult[\"MidNaN\"] = 54] = \"MidNaN\";\n CommandResult[CommandResult[\"MaxNaN\"] = 55] = \"MaxNaN\";\n CommandResult[CommandResult[\"ValueUpperInflectionNaN\"] = 56] = \"ValueUpperInflectionNaN\";\n CommandResult[CommandResult[\"ValueLowerInflectionNaN\"] = 57] = \"ValueLowerInflectionNaN\";\n CommandResult[CommandResult[\"MinInvalidFormula\"] = 58] = \"MinInvalidFormula\";\n CommandResult[CommandResult[\"MidInvalidFormula\"] = 59] = \"MidInvalidFormula\";\n CommandResult[CommandResult[\"MaxInvalidFormula\"] = 60] = \"MaxInvalidFormula\";\n CommandResult[CommandResult[\"ValueUpperInvalidFormula\"] = 61] = \"ValueUpperInvalidFormula\";\n CommandResult[CommandResult[\"ValueLowerInvalidFormula\"] = 62] = \"ValueLowerInvalidFormula\";\n CommandResult[CommandResult[\"InvalidSortZone\"] = 63] = \"InvalidSortZone\";\n CommandResult[CommandResult[\"WaitingSessionConfirmation\"] = 64] = \"WaitingSessionConfirmation\";\n CommandResult[CommandResult[\"MergeOverlap\"] = 65] = \"MergeOverlap\";\n CommandResult[CommandResult[\"TooManyHiddenElements\"] = 66] = \"TooManyHiddenElements\";\n CommandResult[CommandResult[\"Readonly\"] = 67] = \"Readonly\";\n CommandResult[CommandResult[\"InvalidViewportSize\"] = 68] = \"InvalidViewportSize\";\n CommandResult[CommandResult[\"InvalidScrollingDirection\"] = 69] = \"InvalidScrollingDirection\";\n CommandResult[CommandResult[\"FigureDoesNotExist\"] = 70] = \"FigureDoesNotExist\";\n CommandResult[CommandResult[\"InvalidConditionalFormatId\"] = 71] = \"InvalidConditionalFormatId\";\n CommandResult[CommandResult[\"InvalidCellPopover\"] = 72] = \"InvalidCellPopover\";\n CommandResult[CommandResult[\"EmptyTarget\"] = 73] = \"EmptyTarget\";\n CommandResult[CommandResult[\"InvalidFreezeQuantity\"] = 74] = \"InvalidFreezeQuantity\";\n CommandResult[CommandResult[\"FrozenPaneOverlap\"] = 75] = \"FrozenPaneOverlap\";\n CommandResult[CommandResult[\"ValuesNotChanged\"] = 76] = \"ValuesNotChanged\";\n CommandResult[CommandResult[\"InvalidFilterZone\"] = 77] = \"InvalidFilterZone\";\n CommandResult[CommandResult[\"FilterOverlap\"] = 78] = \"FilterOverlap\";\n CommandResult[CommandResult[\"FilterNotFound\"] = 79] = \"FilterNotFound\";\n CommandResult[CommandResult[\"MergeInFilter\"] = 80] = \"MergeInFilter\";\n CommandResult[CommandResult[\"NonContinuousTargets\"] = 81] = \"NonContinuousTargets\";\n CommandResult[CommandResult[\"DuplicatedFigureId\"] = 82] = \"DuplicatedFigureId\";\n CommandResult[CommandResult[\"InvalidSelectionStep\"] = 83] = \"InvalidSelectionStep\";\n CommandResult[CommandResult[\"DuplicatedChartId\"] = 84] = \"DuplicatedChartId\";\n CommandResult[CommandResult[\"ChartDoesNotExist\"] = 85] = \"ChartDoesNotExist\";\n })(exports.CommandResult || (exports.CommandResult = {}));\n\n var DIRECTION;\n (function (DIRECTION) {\n DIRECTION[\"UP\"] = \"up\";\n DIRECTION[\"DOWN\"] = \"down\";\n DIRECTION[\"LEFT\"] = \"left\";\n DIRECTION[\"RIGHT\"] = \"right\";\n })(DIRECTION || (DIRECTION = {}));\n\n var LAYERS;\n (function (LAYERS) {\n LAYERS[LAYERS[\"Background\"] = 0] = \"Background\";\n LAYERS[LAYERS[\"Highlights\"] = 1] = \"Highlights\";\n LAYERS[LAYERS[\"Clipboard\"] = 2] = \"Clipboard\";\n LAYERS[LAYERS[\"Search\"] = 3] = \"Search\";\n LAYERS[LAYERS[\"Chart\"] = 4] = \"Chart\";\n LAYERS[LAYERS[\"Autofill\"] = 5] = \"Autofill\";\n LAYERS[LAYERS[\"Selection\"] = 6] = \"Selection\";\n LAYERS[LAYERS[\"Headers\"] = 7] = \"Headers\";\n })(LAYERS || (LAYERS = {}));\n\n /**\n * This file contains helpers that are common to different charts (mainly\n * line, bar and pie charts)\n */\n /**\n * Adapt ranges of a chart which support DataSet (dataSets and LabelRange).\n */\n function updateChartRangesWithDataSets(getters, applyChange, chartDataSets, chartLabelRange) {\n let isStale = false;\n const dataSetsWithUndefined = [];\n for (let index in chartDataSets) {\n let ds = chartDataSets[index];\n if (ds.labelCell) {\n const labelCell = adaptChartRange(ds.labelCell, applyChange);\n if (ds.labelCell !== labelCell) {\n isStale = true;\n ds = {\n ...ds,\n labelCell: labelCell,\n };\n }\n }\n const dataRange = adaptChartRange(ds.dataRange, applyChange);\n if (dataRange === undefined ||\n getters.getRangeString(dataRange, dataRange.sheetId) === INCORRECT_RANGE_STRING) {\n isStale = true;\n ds = undefined;\n }\n else if (dataRange !== ds.dataRange) {\n isStale = true;\n ds = {\n ...ds,\n dataRange,\n };\n }\n dataSetsWithUndefined[index] = ds;\n }\n let labelRange = chartLabelRange;\n const range = adaptChartRange(labelRange, applyChange);\n if (range !== labelRange) {\n isStale = true;\n labelRange = range;\n }\n const dataSets = dataSetsWithUndefined.filter(isDefined$1);\n return {\n isStale,\n dataSets,\n labelRange,\n };\n }\n /**\n * Copy the dataSets given. All the ranges which are on sheetIdFrom will target\n * sheetIdTo.\n */\n function copyDataSetsWithNewSheetId(sheetIdFrom, sheetIdTo, dataSets) {\n return dataSets.map((ds) => {\n return {\n dataRange: copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, ds.dataRange),\n labelCell: ds.labelCell\n ? copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, ds.labelCell)\n : undefined,\n };\n });\n }\n /**\n * Copy a range. If the range is on the sheetIdFrom, the range will target\n * sheetIdTo.\n */\n function copyLabelRangeWithNewSheetId(sheetIdFrom, sheetIdTo, range) {\n return range ? copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, range) : undefined;\n }\n /**\n * Adapt a single range of a chart\n */\n function adaptChartRange(range, applyChange) {\n if (!range) {\n return undefined;\n }\n const change = applyChange(range);\n switch (change.changeType) {\n case \"NONE\":\n return range;\n case \"REMOVE\":\n return undefined;\n default:\n return change.range;\n }\n }\n /**\n * Create the dataSet objects from xcs\n */\n function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {\n const dataSets = [];\n for (const sheetXC of dataSetsString) {\n const dataRange = getters.getRangeFromSheetXC(sheetId, sheetXC);\n const { unboundedZone: zone, sheetId: dataSetSheetId, invalidSheetName } = dataRange;\n if (invalidSheetName) {\n continue;\n }\n // It's a rectangle. We treat all columns (arbitrary) as different data series.\n if (zone.left !== zone.right && zone.top !== zone.bottom) {\n if (zone.right === undefined) {\n // Should never happens because of the allowDispatch of charts, but just making sure\n continue;\n }\n for (let column = zone.left; column <= zone.right; column++) {\n const columnZone = {\n ...zone,\n left: column,\n right: column,\n };\n dataSets.push(createDataSet(getters, dataSetSheetId, columnZone, dataSetsHaveTitle\n ? {\n top: columnZone.top,\n bottom: columnZone.top,\n left: columnZone.left,\n right: columnZone.left,\n }\n : undefined));\n }\n }\n else if (zone.left === zone.right && zone.top === zone.bottom) {\n // A single cell. If it's only the title, the dataset is not added.\n if (!dataSetsHaveTitle) {\n dataSets.push(createDataSet(getters, dataSetSheetId, zone, undefined));\n }\n }\n else {\n /* 1 row or 1 column */\n dataSets.push(createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle\n ? {\n top: zone.top,\n bottom: zone.top,\n left: zone.left,\n right: zone.left,\n }\n : undefined));\n }\n }\n return dataSets;\n }\n function createDataSet(getters, sheetId, fullZone, titleZone) {\n if (fullZone.left !== fullZone.right && fullZone.top !== fullZone.bottom) {\n throw new Error(`Zone should be a single column or row: ${zoneToXc(fullZone)}`);\n }\n if (titleZone) {\n const dataXC = zoneToXc(fullZone);\n const labelCellXC = zoneToXc(titleZone);\n return {\n labelCell: getters.getRangeFromSheetXC(sheetId, labelCellXC),\n dataRange: getters.getRangeFromSheetXC(sheetId, dataXC),\n };\n }\n else {\n return {\n labelCell: undefined,\n dataRange: getters.getRangeFromSheetXC(sheetId, zoneToXc(fullZone)),\n };\n }\n }\n /**\n * Transform a dataSet to a ExcelDataSet\n */\n function toExcelDataset(getters, ds) {\n var _a;\n const labelZone = (_a = ds.labelCell) === null || _a === void 0 ? void 0 : _a.zone;\n let dataZone = ds.dataRange.zone;\n if (labelZone) {\n const { height, width } = zoneToDimension(dataZone);\n if (height === 1) {\n dataZone = { ...dataZone, left: dataZone.left + 1 };\n }\n else if (width === 1) {\n dataZone = { ...dataZone, top: dataZone.top + 1 };\n }\n }\n const dataRange = ds.dataRange.clone({ zone: dataZone });\n return {\n label: ds.labelCell ? getters.getRangeString(ds.labelCell, \"forceSheetReference\") : undefined,\n range: getters.getRangeString(dataRange, \"forceSheetReference\"),\n };\n }\n /**\n * Transform a chart definition which supports dataSets (dataSets and LabelRange)\n * with an executed command\n */\n function transformChartDefinitionWithDataSetsWithZone(definition, executed) {\n let labelRange;\n if (definition.labelRange) {\n const labelZone = transformZone(toUnboundedZone(definition.labelRange), executed);\n labelRange = labelZone ? zoneToXc(labelZone) : undefined;\n }\n const dataSets = definition.dataSets\n .map(toUnboundedZone)\n .map((zone) => transformZone(zone, executed))\n .filter(isDefined$1)\n .map(zoneToXc);\n return {\n ...definition,\n labelRange,\n dataSets,\n };\n }\n const GraphColors = [\n // the same colors as those used in odoo reporting\n \"rgb(31,119,180)\",\n \"rgb(255,127,14)\",\n \"rgb(174,199,232)\",\n \"rgb(255,187,120)\",\n \"rgb(44,160,44)\",\n \"rgb(152,223,138)\",\n \"rgb(214,39,40)\",\n \"rgb(255,152,150)\",\n \"rgb(148,103,189)\",\n \"rgb(197,176,213)\",\n \"rgb(140,86,75)\",\n \"rgb(196,156,148)\",\n \"rgb(227,119,194)\",\n \"rgb(247,182,210)\",\n \"rgb(127,127,127)\",\n \"rgb(199,199,199)\",\n \"rgb(188,189,34)\",\n \"rgb(219,219,141)\",\n \"rgb(23,190,207)\",\n \"rgb(158,218,229)\",\n ];\n class ChartColors {\n constructor() {\n this.graphColorIndex = 0;\n }\n next() {\n return GraphColors[this.graphColorIndex++ % GraphColors.length];\n }\n }\n /**\n * Choose a font color based on a background color.\n * The font is white with a dark background.\n */\n function chartFontColor(backgroundColor) {\n if (!backgroundColor) {\n return \"#000000\";\n }\n return relativeLuminance(backgroundColor) < 0.3 ? \"#FFFFFF\" : \"#000000\";\n }\n function checkDataset(definition) {\n if (definition.dataSets) {\n const invalidRanges = definition.dataSets.find((range) => !rangeReference.test(range)) !== undefined;\n if (invalidRanges) {\n return 32 /* CommandResult.InvalidDataSet */;\n }\n const zones = definition.dataSets.map(toUnboundedZone);\n if (zones.some((zone) => zone.top !== zone.bottom && isFullRow(zone))) {\n return 32 /* CommandResult.InvalidDataSet */;\n }\n }\n return 0 /* CommandResult.Success */;\n }\n function checkLabelRange(definition) {\n if (definition.labelRange) {\n const invalidLabels = !rangeReference.test(definition.labelRange || \"\");\n if (invalidLabels) {\n return 33 /* CommandResult.InvalidLabelRange */;\n }\n }\n return 0 /* CommandResult.Success */;\n }\n // ---------------------------------------------------------------------------\n // Scorecard\n // ---------------------------------------------------------------------------\n function getBaselineText(baseline, keyValue, baselineMode) {\n if (!baseline) {\n return \"\";\n }\n else if (baselineMode === \"text\" ||\n (keyValue === null || keyValue === void 0 ? void 0 : keyValue.type) !== CellValueType.number ||\n baseline.type !== CellValueType.number) {\n return baseline.formattedValue;\n }\n else {\n let diff = keyValue.value - baseline.value;\n if (baselineMode === \"percentage\" && diff !== 0) {\n diff = (diff / baseline.value) * 100;\n }\n if (baselineMode !== \"percentage\" && baseline.format) {\n return formatValue(diff, baseline.format);\n }\n const baselineStr = Math.abs(parseFloat(diff.toFixed(2))).toLocaleString();\n return baselineMode === \"percentage\" ? baselineStr + \"%\" : baselineStr;\n }\n }\n function getBaselineColor(baseline, baselineMode, keyValue, colorUp, colorDown) {\n if (baselineMode === \"text\" ||\n (baseline === null || baseline === void 0 ? void 0 : baseline.type) !== CellValueType.number ||\n (keyValue === null || keyValue === void 0 ? void 0 : keyValue.type) !== CellValueType.number) {\n return undefined;\n }\n const diff = keyValue.value - baseline.value;\n if (diff > 0) {\n return colorUp;\n }\n else if (diff < 0) {\n return colorDown;\n }\n return undefined;\n }\n function getBaselineArrowDirection(baseline, keyValue, baselineMode) {\n if (baselineMode === \"text\" ||\n (baseline === null || baseline === void 0 ? void 0 : baseline.type) !== CellValueType.number ||\n (keyValue === null || keyValue === void 0 ? void 0 : keyValue.type) !== CellValueType.number) {\n return \"neutral\";\n }\n const diff = keyValue.value - baseline.value;\n if (diff > 0) {\n return \"up\";\n }\n else if (diff < 0) {\n return \"down\";\n }\n return \"neutral\";\n }\n function getChartPositionAtCenterOfViewport(getters, chartSize) {\n const { x, y } = getters.getMainViewportCoordinates();\n const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();\n const { width, height } = getters.getVisibleRect(getters.getActiveMainViewport());\n const position = {\n x: x + scrollX + Math.max(0, (width - chartSize.width) / 2),\n y: y + scrollY + Math.max(0, (height - chartSize.height) / 2),\n }; // Position at the center of the scrollable viewport\n return position;\n }\n\n const CfTerms = {\n Errors: {\n [25 /* CommandResult.InvalidRange */]: _lt(\"The range is invalid\"),\n [51 /* CommandResult.FirstArgMissing */]: _lt(\"The argument is missing. Please provide a value\"),\n [52 /* CommandResult.SecondArgMissing */]: _lt(\"The second argument is missing. Please provide a value\"),\n [53 /* CommandResult.MinNaN */]: _lt(\"The minpoint must be a number\"),\n [54 /* CommandResult.MidNaN */]: _lt(\"The midpoint must be a number\"),\n [55 /* CommandResult.MaxNaN */]: _lt(\"The maxpoint must be a number\"),\n [56 /* CommandResult.ValueUpperInflectionNaN */]: _lt(\"The first value must be a number\"),\n [57 /* CommandResult.ValueLowerInflectionNaN */]: _lt(\"The second value must be a number\"),\n [47 /* CommandResult.MinBiggerThanMax */]: _lt(\"Minimum must be smaller then Maximum\"),\n [50 /* CommandResult.MinBiggerThanMid */]: _lt(\"Minimum must be smaller then Midpoint\"),\n [49 /* CommandResult.MidBiggerThanMax */]: _lt(\"Midpoint must be smaller then Maximum\"),\n [48 /* CommandResult.LowerBiggerThanUpper */]: _lt(\"Lower inflection point must be smaller than upper inflection point\"),\n [58 /* CommandResult.MinInvalidFormula */]: _lt(\"Invalid Minpoint formula\"),\n [60 /* CommandResult.MaxInvalidFormula */]: _lt(\"Invalid Maxpoint formula\"),\n [59 /* CommandResult.MidInvalidFormula */]: _lt(\"Invalid Midpoint formula\"),\n [61 /* CommandResult.ValueUpperInvalidFormula */]: _lt(\"Invalid upper inflection point formula\"),\n [62 /* CommandResult.ValueLowerInvalidFormula */]: _lt(\"Invalid lower inflection point formula\"),\n [24 /* CommandResult.EmptyRange */]: _lt(\"A range needs to be defined\"),\n Unexpected: _lt(\"The rule is invalid for an unknown reason\"),\n },\n ColorScale: _lt(\"Color scale\"),\n IconSet: _lt(\"Icon set\"),\n };\n const CellIsOperators = {\n IsEmpty: _lt(\"Is empty\"),\n IsNotEmpty: _lt(\"Is not empty\"),\n ContainsText: _lt(\"Contains\"),\n NotContains: _lt(\"Does not contain\"),\n BeginsWith: _lt(\"Starts with\"),\n EndsWith: _lt(\"Ends with\"),\n Equal: _lt(\"Is equal to\"),\n NotEqual: _lt(\"Is not equal to\"),\n GreaterThan: _lt(\"Is greater than\"),\n GreaterThanOrEqual: _lt(\"Is greater than or equal to\"),\n LessThan: _lt(\"Is less than\"),\n LessThanOrEqual: _lt(\"Is less than or equal to\"),\n Between: _lt(\"Is between\"),\n NotBetween: _lt(\"Is not between\"),\n };\n const ChartTerms = {\n Series: _lt(\"Series\"),\n Errors: {\n Unexpected: _lt(\"The chart definition is invalid for an unknown reason\"),\n // BASIC CHART ERRORS (LINE | BAR | PIE)\n [32 /* CommandResult.InvalidDataSet */]: _lt(\"The dataset is invalid\"),\n [33 /* CommandResult.InvalidLabelRange */]: _lt(\"Labels are invalid\"),\n // SCORECARD CHART ERRORS\n [34 /* CommandResult.InvalidScorecardKeyValue */]: _lt(\"The key value is invalid\"),\n [35 /* CommandResult.InvalidScorecardBaseline */]: _lt(\"The baseline value is invalid\"),\n // GAUGE CHART ERRORS\n [36 /* CommandResult.InvalidGaugeDataRange */]: _lt(\"The data range is invalid\"),\n [37 /* CommandResult.EmptyGaugeRangeMin */]: _lt(\"A minimum range limit value is needed\"),\n [38 /* CommandResult.GaugeRangeMinNaN */]: _lt(\"The minimum range limit value must be a number\"),\n [39 /* CommandResult.EmptyGaugeRangeMax */]: _lt(\"A maximum range limit value is needed\"),\n [40 /* CommandResult.GaugeRangeMaxNaN */]: _lt(\"The maximum range limit value must be a number\"),\n [41 /* CommandResult.GaugeRangeMinBiggerThanRangeMax */]: _lt(\"Minimum range limit must be smaller than maximum range limit\"),\n [42 /* CommandResult.GaugeLowerInflectionPointNaN */]: _lt(\"The lower inflection point value must be a number\"),\n [43 /* CommandResult.GaugeUpperInflectionPointNaN */]: _lt(\"The upper inflection point value must be a number\"),\n },\n };\n const NumberFormatTerms = {\n Automatic: _lt(\"Automatic\"),\n Number: _lt(\"Number\"),\n Percent: _lt(\"Percent\"),\n Currency: _lt(\"Currency\"),\n CurrencyRounded: _lt(\"Currency rounded\"),\n Date: _lt(\"Date\"),\n Time: _lt(\"Time\"),\n DateTime: _lt(\"Date time\"),\n Duration: _lt(\"Duration\"),\n CustomCurrency: _lt(\"Custom currency\"),\n };\n const CustomCurrencyTerms = {\n Custom: _lt(\"Custom\"),\n };\n const MergeErrorMessage = _lt(\"Merged cells are preventing this operation. Unmerge those cells and try again.\");\n\n /**\n * This file contains helpers that are common to different runtime charts (mainly\n * line, bar and pie charts)\n */\n /**\n * Get the data from a dataSet\n */\n function getData(getters, ds) {\n if (ds.dataRange) {\n const labelCellZone = ds.labelCell ? [zoneToXc(ds.labelCell.zone)] : [];\n const dataXC = recomputeZones([zoneToXc(ds.dataRange.zone)], labelCellZone)[0];\n if (dataXC === undefined) {\n return [];\n }\n const dataRange = getters.getRangeFromSheetXC(ds.dataRange.sheetId, dataXC);\n return getters.getRangeValues(dataRange).map((value) => (value === \"\" ? undefined : value));\n }\n return [];\n }\n function filterEmptyDataPoints(labels, datasets) {\n const numberOfDataPoints = Math.max(labels.length, ...datasets.map((dataset) => { var _a; return ((_a = dataset.data) === null || _a === void 0 ? void 0 : _a.length) || 0; }));\n const dataPointsIndexes = range(0, numberOfDataPoints).filter((dataPointIndex) => {\n const label = labels[dataPointIndex];\n const values = datasets.map((dataset) => { var _a; return (_a = dataset.data) === null || _a === void 0 ? void 0 : _a[dataPointIndex]; });\n return label || values.some((value) => value === 0 || Boolean(value));\n });\n return {\n labels: dataPointsIndexes.map((i) => labels[i] || \"\"),\n dataSetsValues: datasets.map((dataset) => ({\n ...dataset,\n data: dataPointsIndexes.map((i) => dataset.data[i]),\n })),\n };\n }\n /**\n * Aggregates data based on labels\n */\n function aggregateDataForLabels(labels, datasets) {\n const parseNumber = (value) => (typeof value === \"number\" ? value : 0);\n const labelSet = new Set(labels);\n const labelMap = {};\n labelSet.forEach((label) => {\n labelMap[label] = new Array(datasets.length).fill(0);\n });\n for (const indexOfLabel of range(0, labels.length)) {\n const label = labels[indexOfLabel];\n for (const indexOfDataset of range(0, datasets.length)) {\n labelMap[label][indexOfDataset] += parseNumber(datasets[indexOfDataset].data[indexOfLabel]);\n }\n }\n return {\n labels: Object.keys(labelMap),\n dataSetsValues: datasets.map((dataset, indexOfDataset) => ({\n ...dataset,\n data: Object.values(labelMap).map((dataOfLabel) => dataOfLabel[indexOfDataset]),\n })),\n };\n }\n function truncateLabel(label) {\n if (!label) {\n return \"\";\n }\n if (label.length > MAX_CHAR_LABEL) {\n return label.substring(0, MAX_CHAR_LABEL) + \"\u2026\";\n }\n return label;\n }\n /**\n * Get a default chart js configuration\n */\n function getDefaultChartJsRuntime(chart, labels, fontColor) {\n return {\n type: chart.type,\n options: {\n // https://www.chartjs.org/docs/latest/general/responsive.html\n responsive: true,\n maintainAspectRatio: false,\n layout: {\n padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },\n },\n elements: {\n line: {\n fill: false, // do not fill the area under line charts\n },\n point: {\n hitRadius: 15, // increased hit radius to display point tooltip when hovering nearby\n },\n },\n animation: {\n duration: 0, // general animation time\n },\n hover: {\n animationDuration: 10, // duration of animations when hovering an item\n },\n responsiveAnimationDuration: 0,\n title: {\n display: !!chart.title,\n fontSize: 22,\n fontStyle: \"normal\",\n text: _t(chart.title),\n fontColor,\n },\n legend: {\n // Disable default legend onClick (show/hide dataset), to allow us to set a global onClick on the chart container.\n // If we want to re-enable this in the future, we need to override the default onClick to stop the event propagation\n onClick: undefined,\n },\n },\n data: {\n labels: labels.map(truncateLabel),\n datasets: [],\n },\n };\n }\n function getLabelFormat(getters, range) {\n if (!range)\n return undefined;\n return getters.getEvaluatedCell({\n sheetId: range.sheetId,\n col: range.zone.left,\n row: range.zone.top,\n }).format;\n }\n function getChartLabelValues(getters, dataSets, labelRange) {\n let labels = { values: [], formattedValues: [] };\n if (labelRange) {\n if (!labelRange.invalidXc && !labelRange.invalidSheetName) {\n labels = {\n formattedValues: getters.getRangeFormattedValues(labelRange),\n values: getters.getRangeValues(labelRange).map((val) => String(val)),\n };\n }\n }\n else if (dataSets.length === 1) {\n for (let i = 0; i < getData(getters, dataSets[0]).length; i++) {\n labels.formattedValues.push(\"\");\n labels.values.push(\"\");\n }\n }\n else {\n if (dataSets[0]) {\n const ranges = getData(getters, dataSets[0]);\n labels = {\n formattedValues: range(0, ranges.length).map((r) => r.toString()),\n values: labels.formattedValues,\n };\n }\n }\n return labels;\n }\n function getChartDatasetValues(getters, dataSets) {\n const datasetValues = [];\n for (const [dsIndex, ds] of Object.entries(dataSets)) {\n let label;\n if (ds.labelCell) {\n const labelRange = ds.labelCell;\n const cell = labelRange\n ? getters.getEvaluatedCell({\n sheetId: labelRange.sheetId,\n col: labelRange.zone.left,\n row: labelRange.zone.top,\n })\n : undefined;\n label =\n cell && labelRange\n ? truncateLabel(cell.formattedValue)\n : (label = `${ChartTerms.Series} ${parseInt(dsIndex) + 1}`);\n }\n else {\n label = label = `${ChartTerms.Series} ${parseInt(dsIndex) + 1}`;\n }\n let data = ds.dataRange ? getData(getters, ds) : [];\n datasetValues.push({ data, label });\n }\n return datasetValues;\n }\n /** See https://www.chartjs.org/docs/latest/charts/area.html#filling-modes */\n function getFillingMode(index) {\n if (index === 0) {\n return \"origin\";\n }\n else {\n return index - 1;\n }\n }\n\n class BarChart extends AbstractChart {\n constructor(definition, sheetId, getters) {\n super(definition, sheetId, getters);\n this.type = \"bar\";\n this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);\n this.labelRange = createRange(getters, sheetId, definition.labelRange);\n this.background = definition.background;\n this.verticalAxisPosition = definition.verticalAxisPosition;\n this.legendPosition = definition.legendPosition;\n this.stacked = definition.stacked;\n this.aggregated = definition.aggregated;\n }\n static transformDefinition(definition, executed) {\n return transformChartDefinitionWithDataSetsWithZone(definition, executed);\n }\n static validateChartDefinition(validator, definition) {\n return validator.checkValidations(definition, checkDataset, checkLabelRange);\n }\n static getDefinitionFromContextCreation(context) {\n return {\n background: context.background,\n dataSets: context.range ? context.range : [],\n dataSetsHaveTitle: false,\n stacked: false,\n aggregated: false,\n legendPosition: \"top\",\n title: context.title || \"\",\n type: \"bar\",\n verticalAxisPosition: \"left\",\n labelRange: context.auxiliaryRange || undefined,\n };\n }\n getContextCreation() {\n return {\n background: this.background,\n title: this.title,\n range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),\n auxiliaryRange: this.labelRange\n ? this.getters.getRangeString(this.labelRange, this.sheetId)\n : undefined,\n };\n }\n copyForSheetId(sheetId) {\n const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);\n const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);\n const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);\n return new BarChart(definition, sheetId, this.getters);\n }\n copyInSheetId(sheetId) {\n const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);\n return new BarChart(definition, sheetId, this.getters);\n }\n getDefinition() {\n return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);\n }\n getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {\n return {\n type: \"bar\",\n dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,\n background: this.background,\n dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),\n legendPosition: this.legendPosition,\n verticalAxisPosition: this.verticalAxisPosition,\n labelRange: labelRange\n ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)\n : undefined,\n title: this.title,\n stacked: this.stacked,\n aggregated: this.aggregated,\n };\n }\n getDefinitionForExcel() {\n // Excel does not support aggregating labels\n if (this.aggregated)\n return undefined;\n const dataSets = this.dataSets\n .map((ds) => toExcelDataset(this.getters, ds))\n .filter((ds) => ds.range !== \"\"); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?\n return {\n ...this.getDefinition(),\n backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),\n fontColor: toXlsxHexColor(chartFontColor(this.background)),\n dataSets,\n };\n }\n updateRanges(applyChange) {\n const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);\n if (!isStale) {\n return this;\n }\n const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);\n return new BarChart(definition, this.sheetId, this.getters);\n }\n }\n function getBarConfiguration(chart, labels) {\n var _a;\n const fontColor = chartFontColor(chart.background);\n const config = getDefaultChartJsRuntime(chart, labels, fontColor);\n const legend = {\n labels: { fontColor },\n };\n if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === \"none\") {\n legend.display = false;\n }\n else {\n legend.position = chart.legendPosition;\n }\n config.options.legend = { ...(_a = config.options) === null || _a === void 0 ? void 0 : _a.legend, ...legend };\n config.options.layout = {\n padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },\n };\n config.options.scales = {\n xAxes: [\n {\n ticks: {\n // x axis configuration\n maxRotation: 60,\n minRotation: 15,\n padding: 5,\n labelOffset: 2,\n fontColor,\n },\n },\n ],\n yAxes: [\n {\n position: chart.verticalAxisPosition,\n ticks: {\n fontColor,\n // y axis configuration\n beginAtZero: true, // the origin of the y axis is always zero\n },\n },\n ],\n };\n if (chart.stacked) {\n config.options.scales.xAxes[0].stacked = true;\n config.options.scales.yAxes[0].stacked = true;\n }\n return config;\n }\n function createBarChartRuntime(chart, getters) {\n const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);\n let labels = labelValues.formattedValues;\n let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);\n ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));\n if (chart.aggregated) {\n ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));\n }\n const config = getBarConfiguration(chart, labels);\n const colors = new ChartColors();\n for (let { label, data } of dataSetsValues) {\n const color = colors.next();\n const dataset = {\n label,\n data,\n borderColor: color,\n backgroundColor: color,\n };\n config.data.datasets.push(dataset);\n }\n return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };\n }\n\n function isDataRangeValid(definition) {\n return definition.dataRange && !rangeReference.test(definition.dataRange)\n ? 36 /* CommandResult.InvalidGaugeDataRange */\n : 0 /* CommandResult.Success */;\n }\n function checkRangeLimits(check, batchValidations) {\n return batchValidations((definition) => {\n if (definition.sectionRule) {\n return check(definition.sectionRule.rangeMin, \"rangeMin\");\n }\n return 0 /* CommandResult.Success */;\n }, (definition) => {\n if (definition.sectionRule) {\n return check(definition.sectionRule.rangeMax, \"rangeMax\");\n }\n return 0 /* CommandResult.Success */;\n });\n }\n function checkInflectionPointsValue(check, batchValidations) {\n return batchValidations((definition) => {\n if (definition.sectionRule) {\n return check(definition.sectionRule.lowerInflectionPoint.value, \"lowerInflectionPointValue\");\n }\n return 0 /* CommandResult.Success */;\n }, (definition) => {\n if (definition.sectionRule) {\n return check(definition.sectionRule.upperInflectionPoint.value, \"upperInflectionPointValue\");\n }\n return 0 /* CommandResult.Success */;\n });\n }\n function checkRangeMinBiggerThanRangeMax(definition) {\n if (definition.sectionRule) {\n if (Number(definition.sectionRule.rangeMin) >= Number(definition.sectionRule.rangeMax)) {\n return 41 /* CommandResult.GaugeRangeMinBiggerThanRangeMax */;\n }\n }\n return 0 /* CommandResult.Success */;\n }\n function checkEmpty(value, valueName) {\n if (value === \"\") {\n switch (valueName) {\n case \"rangeMin\":\n return 37 /* CommandResult.EmptyGaugeRangeMin */;\n case \"rangeMax\":\n return 39 /* CommandResult.EmptyGaugeRangeMax */;\n }\n }\n return 0 /* CommandResult.Success */;\n }\n function checkNaN(value, valueName) {\n if (isNaN(value)) {\n switch (valueName) {\n case \"rangeMin\":\n return 38 /* CommandResult.GaugeRangeMinNaN */;\n case \"rangeMax\":\n return 40 /* CommandResult.GaugeRangeMaxNaN */;\n case \"lowerInflectionPointValue\":\n return 42 /* CommandResult.GaugeLowerInflectionPointNaN */;\n case \"upperInflectionPointValue\":\n return 43 /* CommandResult.GaugeUpperInflectionPointNaN */;\n }\n }\n return 0 /* CommandResult.Success */;\n }\n class GaugeChart extends AbstractChart {\n constructor(definition, sheetId, getters) {\n super(definition, sheetId, getters);\n this.type = \"gauge\";\n this.dataRange = createRange(this.getters, this.sheetId, definition.dataRange);\n this.sectionRule = definition.sectionRule;\n this.background = definition.background;\n }\n static validateChartDefinition(validator, definition) {\n return validator.checkValidations(definition, isDataRangeValid, validator.chainValidations(checkRangeLimits(checkEmpty, validator.batchValidations), checkRangeLimits(checkNaN, validator.batchValidations), checkRangeMinBiggerThanRangeMax), validator.chainValidations(checkInflectionPointsValue(checkNaN, validator.batchValidations)));\n }\n static transformDefinition(definition, executed) {\n let dataRangeZone;\n if (definition.dataRange) {\n dataRangeZone = transformZone(toUnboundedZone(definition.dataRange), executed);\n }\n return {\n ...definition,\n dataRange: dataRangeZone ? zoneToXc(dataRangeZone) : undefined,\n };\n }\n static getDefinitionFromContextCreation(context) {\n return {\n background: context.background,\n title: context.title || \"\",\n type: \"gauge\",\n dataRange: context.range ? context.range[0] : undefined,\n sectionRule: {\n colors: {\n lowerColor: DEFAULT_GAUGE_LOWER_COLOR,\n middleColor: DEFAULT_GAUGE_MIDDLE_COLOR,\n upperColor: DEFAULT_GAUGE_UPPER_COLOR,\n },\n rangeMin: \"0\",\n rangeMax: \"100\",\n lowerInflectionPoint: {\n type: \"percentage\",\n value: \"15\",\n },\n upperInflectionPoint: {\n type: \"percentage\",\n value: \"40\",\n },\n },\n };\n }\n copyForSheetId(sheetId) {\n const dataRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.dataRange);\n const definition = this.getDefinitionWithSpecificRanges(dataRange, sheetId);\n return new GaugeChart(definition, sheetId, this.getters);\n }\n copyInSheetId(sheetId) {\n const definition = this.getDefinitionWithSpecificRanges(this.dataRange, sheetId);\n return new GaugeChart(definition, sheetId, this.getters);\n }\n getDefinition() {\n return this.getDefinitionWithSpecificRanges(this.dataRange);\n }\n getDefinitionWithSpecificRanges(dataRange, targetSheetId) {\n return {\n background: this.background,\n sectionRule: this.sectionRule,\n title: this.title,\n type: \"gauge\",\n dataRange: dataRange\n ? this.getters.getRangeString(dataRange, targetSheetId || this.sheetId)\n : undefined,\n };\n }\n getDefinitionForExcel() {\n // This kind of graph is not exportable in Excel\n return undefined;\n }\n getContextCreation() {\n return {\n background: this.background,\n title: this.title,\n range: this.dataRange\n ? [this.getters.getRangeString(this.dataRange, this.sheetId)]\n : undefined,\n };\n }\n updateRanges(applyChange) {\n const range = adaptChartRange(this.dataRange, applyChange);\n if (this.dataRange === range) {\n return this;\n }\n const definition = this.getDefinitionWithSpecificRanges(range);\n return new GaugeChart(definition, this.sheetId, this.getters);\n }\n }\n function getGaugeConfiguration(chart) {\n const fontColor = chartFontColor(chart.background);\n const config = getDefaultChartJsRuntime(chart, [], fontColor);\n config.options.hover = undefined;\n config.options.events = [];\n config.options.layout = {\n padding: { left: 30, right: 30, top: chart.title ? 10 : 25, bottom: 25 },\n };\n config.options.needle = {\n radiusPercentage: 2,\n widthPercentage: 3.2,\n lengthPercentage: 80,\n color: \"#000000\",\n };\n config.options.valueLabel = {\n display: false,\n formatter: null,\n color: \"#FFFFFF\",\n backgroundColor: \"#000000\",\n fontSize: 30,\n borderRadius: 5,\n padding: {\n top: 5,\n right: 5,\n bottom: 5,\n left: 5,\n },\n bottomMarginPercentage: 5,\n };\n return config;\n }\n function createGaugeChartRuntime(chart, getters) {\n const config = getGaugeConfiguration(chart);\n const colors = chart.sectionRule.colors;\n const lowerPoint = chart.sectionRule.lowerInflectionPoint;\n const upperPoint = chart.sectionRule.upperInflectionPoint;\n const lowerPointValue = Number(lowerPoint.value);\n const upperPointValue = Number(upperPoint.value);\n const minNeedleValue = Number(chart.sectionRule.rangeMin);\n const maxNeedleValue = Number(chart.sectionRule.rangeMax);\n const needleCoverage = maxNeedleValue - minNeedleValue;\n const needleInflectionPoint = [];\n if (lowerPoint.value !== \"\") {\n const lowerPointNeedleValue = lowerPoint.type === \"number\"\n ? lowerPointValue\n : minNeedleValue + (needleCoverage * lowerPointValue) / 100;\n needleInflectionPoint.push({\n value: clip(lowerPointNeedleValue, minNeedleValue, maxNeedleValue),\n color: colors.lowerColor,\n });\n }\n if (upperPoint.value !== \"\") {\n const upperPointNeedleValue = upperPoint.type === \"number\"\n ? upperPointValue\n : minNeedleValue + (needleCoverage * upperPointValue) / 100;\n needleInflectionPoint.push({\n value: clip(upperPointNeedleValue, minNeedleValue, maxNeedleValue),\n color: colors.middleColor,\n });\n }\n const data = [];\n const backgroundColor = [];\n needleInflectionPoint\n .sort((a, b) => a.value - b.value)\n .map((point) => {\n data.push(point.value);\n backgroundColor.push(point.color);\n });\n // There's a bug in gauge lib when the last element in `data` is 0 (i.e. when the range maximum is 0).\n // The value wrongly fallbacks to 1 because 0 is falsy\n // See https://github.com/haiiaaa/chartjs-gauge/pull/33\n // https://github.com/haiiaaa/chartjs-gauge/blob/2ea50541d754d710cb30c2502fa690ac5dc27afd/src/controllers/controller.gauge.js#L52\n data.push(maxNeedleValue);\n backgroundColor.push(colors.upperColor);\n const dataRange = chart.dataRange;\n const deltaBeyondRangeLimit = needleCoverage / 30;\n let needleValue = minNeedleValue - deltaBeyondRangeLimit; // make needle value always at the minimum by default\n let cellFormatter = null;\n let displayValue = false;\n if (dataRange !== undefined) {\n const cell = getters.getEvaluatedCell({\n sheetId: dataRange.sheetId,\n col: dataRange.zone.left,\n row: dataRange.zone.top,\n });\n if (cell.type === CellValueType.number) {\n // in gauge graph \"datasets.value\" is used to calculate the angle of the\n // needle in the graph. To prevent the needle from making 360\u00b0 turns, we\n // clip the value between a min and a max. This min and this max are slightly\n // smaller and slightly larger than minRange and maxRange to mark the fact\n // that the needle is out of the range limits\n needleValue = clip(cell.value, minNeedleValue - deltaBeyondRangeLimit, maxNeedleValue + deltaBeyondRangeLimit);\n cellFormatter = () => getters.getRangeFormattedValues(dataRange)[0];\n displayValue = true;\n }\n }\n config.options.valueLabel.display = displayValue;\n config.options.valueLabel.formatter = cellFormatter;\n config.data.datasets.push({\n data,\n minValue: Number(chart.sectionRule.rangeMin),\n value: needleValue,\n backgroundColor,\n });\n return {\n chartJsConfig: config,\n background: getters.getBackgroundOfSingleCellChart(chart.background, dataRange),\n };\n }\n\n const UNIT_LENGTH = {\n second: 1000,\n minute: 1000 * 60,\n hour: 1000 * 3600,\n day: 1000 * 3600 * 24,\n month: 1000 * 3600 * 24 * 30,\n year: 1000 * 3600 * 24 * 365,\n };\n const Milliseconds = {\n inSeconds: function (milliseconds) {\n return Math.floor(milliseconds / UNIT_LENGTH.second);\n },\n inMinutes: function (milliseconds) {\n return Math.floor(milliseconds / UNIT_LENGTH.minute);\n },\n inHours: function (milliseconds) {\n return Math.floor(milliseconds / UNIT_LENGTH.hour);\n },\n inDays: function (milliseconds) {\n return Math.floor(milliseconds / UNIT_LENGTH.day);\n },\n inMonths: function (milliseconds) {\n return Math.floor(milliseconds / UNIT_LENGTH.month);\n },\n inYears: function (milliseconds) {\n return Math.floor(milliseconds / UNIT_LENGTH.year);\n },\n };\n /**\n * Regex to test if a format string is a date format that can be translated into a moment time format\n */\n const timeFormatMomentCompatible = /^((d|dd|m|mm|yyyy|yy|hh|h|ss|a)(-|:|\\s|\\/))*(d|dd|m|mm|yyyy|yy|hh|h|ss|a)$/i;\n /** Get the time options for the XAxis of ChartJS */\n function getChartTimeOptions(labels, labelFormat) {\n const momentFormat = convertDateFormatForMoment(labelFormat);\n const timeUnit = getBestTimeUnitForScale(labels, momentFormat);\n const displayFormats = {};\n if (timeUnit) {\n displayFormats[timeUnit] = momentFormat;\n }\n return {\n parser: momentFormat,\n displayFormats,\n unit: timeUnit,\n };\n }\n /**\n * Convert the given date format into a format that moment.js understands.\n *\n * https://momentjs.com/docs/#/parsing/string-format/\n */\n function convertDateFormatForMoment(format) {\n format = format.replace(/y/g, \"Y\");\n format = format.replace(/d/g, \"D\");\n // \"m\" before \"h\" == month, \"m\" after \"h\" == minute\n const indexH = format.indexOf(\"h\");\n if (indexH >= 0) {\n format = format.slice(0, indexH).replace(/m/g, \"M\") + format.slice(indexH);\n }\n else {\n format = format.replace(/m/g, \"M\");\n }\n // If we have an \"a\", we should display hours as AM/PM (h), otherwise display 24 hours format (H)\n if (!format.includes(\"a\")) {\n format = format.replace(/h/g, \"H\");\n }\n return format;\n }\n /** Get the minimum time unit that the format is able to display */\n function getFormatMinDisplayUnit(format) {\n if (format.includes(\"s\")) {\n return \"second\";\n }\n else if (format.includes(\"m\")) {\n return \"minute\";\n }\n else if (format.includes(\"h\") || format.includes(\"H\")) {\n return \"hour\";\n }\n else if (format.includes(\"D\")) {\n return \"day\";\n }\n else if (format.includes(\"M\")) {\n return \"month\";\n }\n return \"year\";\n }\n /**\n * Returns the best time unit that should be used for the X axis of a chart in order to display all\n * the labels correctly.\n *\n * There is two conditions :\n * - the format of the labels should be able to display the unit. For example if the format is \"DD/MM/YYYY\"\n * it makes no sense to try to use minutes in the X axis\n * - we want the \"best fit\" unit. For example if the labels span a period of several days, we want to use days\n * as a unit, but if they span 200 days, we'd like to use months instead\n *\n */\n function getBestTimeUnitForScale(labels, format) {\n const labelDates = labels.map((label) => { var _a; return (_a = parseDateTime(label)) === null || _a === void 0 ? void 0 : _a.jsDate; });\n if (labelDates.some((date) => date === undefined) || labels.length < 2) {\n return undefined;\n }\n const labelsTimestamps = labelDates.map((date) => date.getTime());\n const period = Math.max(...labelsTimestamps) - Math.min(...labelsTimestamps);\n const minUnit = getFormatMinDisplayUnit(format);\n if (UNIT_LENGTH.second >= UNIT_LENGTH[minUnit] && Milliseconds.inSeconds(period) < 180) {\n return \"second\";\n }\n else if (UNIT_LENGTH.minute >= UNIT_LENGTH[minUnit] && Milliseconds.inMinutes(period) < 180) {\n return \"minute\";\n }\n else if (UNIT_LENGTH.hour >= UNIT_LENGTH[minUnit] && Milliseconds.inHours(period) < 96) {\n return \"hour\";\n }\n else if (UNIT_LENGTH.day >= UNIT_LENGTH[minUnit] && Milliseconds.inDays(period) < 90) {\n return \"day\";\n }\n else if (UNIT_LENGTH.month >= UNIT_LENGTH[minUnit] && Milliseconds.inMonths(period) < 36) {\n return \"month\";\n }\n return \"year\";\n }\n\n class LineChart extends AbstractChart {\n constructor(definition, sheetId, getters) {\n super(definition, sheetId, getters);\n this.type = \"line\";\n this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);\n this.labelRange = createRange(this.getters, sheetId, definition.labelRange);\n this.background = definition.background;\n this.verticalAxisPosition = definition.verticalAxisPosition;\n this.legendPosition = definition.legendPosition;\n this.labelsAsText = definition.labelsAsText;\n this.stacked = definition.stacked;\n this.aggregated = definition.aggregated;\n }\n static validateChartDefinition(validator, definition) {\n return validator.checkValidations(definition, checkDataset, checkLabelRange);\n }\n static transformDefinition(definition, executed) {\n return transformChartDefinitionWithDataSetsWithZone(definition, executed);\n }\n static getDefinitionFromContextCreation(context) {\n return {\n background: context.background,\n dataSets: context.range ? context.range : [],\n dataSetsHaveTitle: false,\n labelsAsText: false,\n legendPosition: \"top\",\n title: context.title || \"\",\n type: \"line\",\n verticalAxisPosition: \"left\",\n labelRange: context.auxiliaryRange || undefined,\n stacked: false,\n aggregated: false,\n };\n }\n getDefinition() {\n return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);\n }\n getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {\n return {\n type: \"line\",\n dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,\n background: this.background,\n dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),\n legendPosition: this.legendPosition,\n verticalAxisPosition: this.verticalAxisPosition,\n labelRange: labelRange\n ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)\n : undefined,\n title: this.title,\n labelsAsText: this.labelsAsText,\n stacked: this.stacked,\n aggregated: this.aggregated,\n };\n }\n getContextCreation() {\n return {\n background: this.background,\n title: this.title,\n range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),\n auxiliaryRange: this.labelRange\n ? this.getters.getRangeString(this.labelRange, this.sheetId)\n : undefined,\n };\n }\n updateRanges(applyChange) {\n const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);\n if (!isStale) {\n return this;\n }\n const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);\n return new LineChart(definition, this.sheetId, this.getters);\n }\n getDefinitionForExcel() {\n // Excel does not support aggregating labels\n if (this.aggregated)\n return undefined;\n const dataSets = this.dataSets\n .map((ds) => toExcelDataset(this.getters, ds))\n .filter((ds) => ds.range !== \"\"); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?\n return {\n ...this.getDefinition(),\n backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),\n fontColor: toXlsxHexColor(chartFontColor(this.background)),\n dataSets,\n };\n }\n copyForSheetId(sheetId) {\n const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);\n const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);\n const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);\n return new LineChart(definition, sheetId, this.getters);\n }\n copyInSheetId(sheetId) {\n const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);\n return new LineChart(definition, sheetId, this.getters);\n }\n }\n function fixEmptyLabelsForDateCharts(labels, dataSetsValues) {\n if (labels.length === 0 || labels.every((label) => !label)) {\n return { labels, dataSetsValues };\n }\n const newLabels = [...labels];\n const newDatasets = deepCopy(dataSetsValues);\n for (let i = 0; i < newLabels.length; i++) {\n if (!newLabels[i]) {\n newLabels[i] = findNextDefinedValue(newLabels, i);\n for (let ds of newDatasets) {\n ds.data[i] = undefined;\n }\n }\n }\n return { labels: newLabels, dataSetsValues: newDatasets };\n }\n function canChartParseLabels(labelRange, getters) {\n return canBeDateChart(labelRange, getters) || canBeLinearChart(labelRange, getters);\n }\n function getChartAxisType(chart, getters) {\n if (isDateChart(chart, getters)) {\n return \"time\";\n }\n if (isLinearChart(chart, getters)) {\n return \"linear\";\n }\n return \"category\";\n }\n function isDateChart(chart, getters) {\n return !chart.labelsAsText && canBeDateChart(chart.labelRange, getters);\n }\n function isLinearChart(chart, getters) {\n return !chart.labelsAsText && canBeLinearChart(chart.labelRange, getters);\n }\n function canBeDateChart(labelRange, getters) {\n if (!labelRange || !canBeLinearChart(labelRange, getters)) {\n return false;\n }\n const labelFormat = getters.getEvaluatedCell({\n sheetId: labelRange.sheetId,\n col: labelRange.zone.left,\n row: labelRange.zone.top,\n }).format;\n return Boolean(labelFormat && timeFormatMomentCompatible.test(labelFormat));\n }\n function canBeLinearChart(labelRange, getters) {\n if (!labelRange) {\n return false;\n }\n const labels = getters.getRangeValues(labelRange);\n if (labels.some((label) => isNaN(Number(label)) && label)) {\n return false;\n }\n if (labels.every((label) => !label)) {\n return false;\n }\n return true;\n }\n function getLineConfiguration(chart, labels) {\n var _a;\n const fontColor = chartFontColor(chart.background);\n const config = getDefaultChartJsRuntime(chart, labels, fontColor);\n const legend = {\n labels: {\n fontColor,\n generateLabels(chart) {\n const { data } = chart;\n const labels = window.Chart.defaults.global.legend.labels.generateLabels(chart);\n for (const [index, label] of labels.entries()) {\n label.fillStyle = data.datasets[index].borderColor;\n }\n return labels;\n },\n },\n };\n if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === \"none\") {\n legend.display = false;\n }\n else {\n legend.position = chart.legendPosition;\n }\n config.options.legend = { ...(_a = config.options) === null || _a === void 0 ? void 0 : _a.legend, ...legend };\n config.options.layout = {\n padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },\n };\n config.options.scales = {\n xAxes: [\n {\n ticks: {\n // x axis configuration\n maxRotation: 60,\n minRotation: 15,\n padding: 5,\n labelOffset: 2,\n fontColor,\n },\n },\n ],\n yAxes: [\n {\n position: chart.verticalAxisPosition,\n ticks: {\n fontColor,\n // y axis configuration\n beginAtZero: true, // the origin of the y axis is always zero\n },\n },\n ],\n };\n if (chart.stacked) {\n config.options.scales.yAxes[0].stacked = true;\n }\n return config;\n }\n function createLineChartRuntime(chart, getters) {\n const axisType = getChartAxisType(chart, getters);\n const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);\n let labels = axisType === \"linear\" ? labelValues.values : labelValues.formattedValues;\n let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);\n ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));\n if (axisType === \"time\") {\n ({ labels, dataSetsValues } = fixEmptyLabelsForDateCharts(labels, dataSetsValues));\n }\n if (chart.aggregated) {\n ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));\n }\n const config = getLineConfiguration(chart, labels);\n const labelFormat = getLabelFormat(getters, chart.labelRange);\n if (axisType === \"time\") {\n config.options.scales.xAxes[0].type = \"time\";\n config.options.scales.xAxes[0].time = getChartTimeOptions(labels, labelFormat);\n config.options.scales.xAxes[0].ticks.maxTicksLimit = 15;\n }\n else if (axisType === \"linear\") {\n config.options.scales.xAxes[0].type = \"linear\";\n config.options.scales.xAxes[0].ticks.callback = (value) => formatValue(value, labelFormat);\n }\n const colors = new ChartColors();\n for (let [index, { label, data }] of dataSetsValues.entries()) {\n if ([\"linear\", \"time\"].includes(axisType)) {\n // Replace empty string labels by undefined to make sure chartJS doesn't decide that \"\" is the same as 0\n data = data.map((y, index) => ({ x: labels[index] || undefined, y }));\n }\n const color = colors.next();\n let backgroundRGBA = colorToRGBA(color);\n if (chart.stacked) {\n backgroundRGBA.a = LINE_FILL_TRANSPARENCY;\n }\n const backgroundColor = rgbaToHex(backgroundRGBA);\n const dataset = {\n label,\n data,\n lineTension: 0,\n borderColor: color,\n backgroundColor,\n pointBackgroundColor: color,\n fill: chart.stacked ? getFillingMode(index) : false,\n };\n config.data.datasets.push(dataset);\n }\n return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };\n }\n\n class PieChart extends AbstractChart {\n constructor(definition, sheetId, getters) {\n super(definition, sheetId, getters);\n this.type = \"pie\";\n this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);\n this.labelRange = createRange(getters, sheetId, definition.labelRange);\n this.background = definition.background;\n this.legendPosition = definition.legendPosition;\n this.aggregated = definition.aggregated;\n }\n static transformDefinition(definition, executed) {\n return transformChartDefinitionWithDataSetsWithZone(definition, executed);\n }\n static validateChartDefinition(validator, definition) {\n return validator.checkValidations(definition, checkDataset, checkLabelRange);\n }\n static getDefinitionFromContextCreation(context) {\n return {\n background: context.background,\n dataSets: context.range ? context.range : [],\n dataSetsHaveTitle: false,\n legendPosition: \"top\",\n title: context.title || \"\",\n type: \"pie\",\n labelRange: context.auxiliaryRange || undefined,\n aggregated: false,\n };\n }\n getDefinition() {\n return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);\n }\n getContextCreation() {\n return {\n background: this.background,\n title: this.title,\n range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),\n auxiliaryRange: this.labelRange\n ? this.getters.getRangeString(this.labelRange, this.sheetId)\n : undefined,\n };\n }\n getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {\n return {\n type: \"pie\",\n dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,\n background: this.background,\n dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),\n legendPosition: this.legendPosition,\n labelRange: labelRange\n ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)\n : undefined,\n title: this.title,\n aggregated: this.aggregated,\n };\n }\n copyForSheetId(sheetId) {\n const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);\n const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);\n const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);\n return new PieChart(definition, sheetId, this.getters);\n }\n copyInSheetId(sheetId) {\n const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);\n return new PieChart(definition, sheetId, this.getters);\n }\n getDefinitionForExcel() {\n // Excel does not support aggregating labels\n if (this.aggregated)\n return undefined;\n const dataSets = this.dataSets\n .map((ds) => toExcelDataset(this.getters, ds))\n .filter((ds) => ds.range !== \"\"); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?\n return {\n ...this.getDefinition(),\n backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),\n fontColor: toXlsxHexColor(chartFontColor(this.background)),\n verticalAxisPosition: \"left\",\n dataSets,\n };\n }\n updateRanges(applyChange) {\n const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);\n if (!isStale) {\n return this;\n }\n const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);\n return new PieChart(definition, this.sheetId, this.getters);\n }\n }\n function getPieConfiguration(chart, labels) {\n var _a;\n const fontColor = chartFontColor(chart.background);\n const config = getDefaultChartJsRuntime(chart, labels, fontColor);\n const legend = {\n labels: { fontColor },\n };\n if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === \"none\") {\n legend.display = false;\n }\n else {\n legend.position = chart.legendPosition;\n }\n config.options.legend = { ...(_a = config.options) === null || _a === void 0 ? void 0 : _a.legend, ...legend };\n config.options.layout = {\n padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },\n };\n config.options.tooltips = {\n callbacks: {\n title: function (tooltipItems, data) {\n return data.datasets[tooltipItems[0].datasetIndex].label;\n },\n },\n };\n return config;\n }\n function getPieColors(colors, dataSetsValues) {\n const pieColors = [];\n const maxLength = Math.max(...dataSetsValues.map((ds) => ds.data.length));\n for (let i = 0; i <= maxLength; i++) {\n pieColors.push(colors.next());\n }\n return pieColors;\n }\n function createPieChartRuntime(chart, getters) {\n const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);\n let labels = labelValues.formattedValues;\n let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);\n ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));\n if (chart.aggregated) {\n ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));\n }\n const config = getPieConfiguration(chart, labels);\n const colors = new ChartColors();\n for (let { label, data } of dataSetsValues) {\n const backgroundColor = getPieColors(colors, dataSetsValues);\n const dataset = {\n label,\n data,\n borderColor: \"#FFFFFF\",\n backgroundColor,\n };\n config.data.datasets.push(dataset);\n }\n return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };\n }\n\n function checkKeyValue(definition) {\n return definition.keyValue && !rangeReference.test(definition.keyValue)\n ? 34 /* CommandResult.InvalidScorecardKeyValue */\n : 0 /* CommandResult.Success */;\n }\n function checkBaseline(definition) {\n return definition.baseline && !rangeReference.test(definition.baseline)\n ? 35 /* CommandResult.InvalidScorecardBaseline */\n : 0 /* CommandResult.Success */;\n }\n class ScorecardChart extends AbstractChart {\n constructor(definition, sheetId, getters) {\n super(definition, sheetId, getters);\n this.type = \"scorecard\";\n this.keyValue = createRange(getters, sheetId, definition.keyValue);\n this.baseline = createRange(getters, sheetId, definition.baseline);\n this.baselineMode = definition.baselineMode;\n this.baselineDescr = definition.baselineDescr;\n this.background = definition.background;\n this.baselineColorUp = definition.baselineColorUp;\n this.baselineColorDown = definition.baselineColorDown;\n }\n static validateChartDefinition(validator, definition) {\n return validator.checkValidations(definition, checkKeyValue, checkBaseline);\n }\n static getDefinitionFromContextCreation(context) {\n return {\n background: context.background,\n type: \"scorecard\",\n keyValue: context.range ? context.range[0] : undefined,\n title: context.title || \"\",\n baselineMode: DEFAULT_SCORECARD_BASELINE_MODE,\n baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,\n baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,\n baseline: context.auxiliaryRange || \"\",\n };\n }\n static transformDefinition(definition, executed) {\n let baselineZone;\n let keyValueZone;\n if (definition.baseline) {\n baselineZone = transformZone(toUnboundedZone(definition.baseline), executed);\n }\n if (definition.keyValue) {\n keyValueZone = transformZone(toUnboundedZone(definition.keyValue), executed);\n }\n return {\n ...definition,\n baseline: baselineZone ? zoneToXc(baselineZone) : undefined,\n keyValue: keyValueZone ? zoneToXc(keyValueZone) : undefined,\n };\n }\n copyForSheetId(sheetId) {\n const baseline = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.baseline);\n const keyValue = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.keyValue);\n const definition = this.getDefinitionWithSpecificRanges(baseline, keyValue, sheetId);\n return new ScorecardChart(definition, sheetId, this.getters);\n }\n copyInSheetId(sheetId) {\n const definition = this.getDefinitionWithSpecificRanges(this.baseline, this.keyValue, sheetId);\n return new ScorecardChart(definition, sheetId, this.getters);\n }\n getDefinition() {\n return this.getDefinitionWithSpecificRanges(this.baseline, this.keyValue);\n }\n getContextCreation() {\n return {\n background: this.background,\n title: this.title,\n range: this.keyValue ? [this.getters.getRangeString(this.keyValue, this.sheetId)] : undefined,\n auxiliaryRange: this.baseline\n ? this.getters.getRangeString(this.baseline, this.sheetId)\n : undefined,\n };\n }\n getDefinitionWithSpecificRanges(baseline, keyValue, targetSheetId) {\n return {\n baselineColorDown: this.baselineColorDown,\n baselineColorUp: this.baselineColorUp,\n baselineMode: this.baselineMode,\n title: this.title,\n type: \"scorecard\",\n background: this.background,\n baseline: baseline\n ? this.getters.getRangeString(baseline, targetSheetId || this.sheetId)\n : undefined,\n baselineDescr: this.baselineDescr,\n keyValue: keyValue\n ? this.getters.getRangeString(keyValue, targetSheetId || this.sheetId)\n : undefined,\n };\n }\n getDefinitionForExcel() {\n // This kind of graph is not exportable in Excel\n return undefined;\n }\n updateRanges(applyChange) {\n const baseline = adaptChartRange(this.baseline, applyChange);\n const keyValue = adaptChartRange(this.keyValue, applyChange);\n if (this.baseline === baseline && this.keyValue === keyValue) {\n return this;\n }\n const definition = this.getDefinitionWithSpecificRanges(baseline, keyValue);\n return new ScorecardChart(definition, this.sheetId, this.getters);\n }\n }\n function createScorecardChartRuntime(chart, getters) {\n let keyValue = \"\";\n let formattedKeyValue = \"\";\n let keyValueCell;\n if (chart.keyValue) {\n const keyValuePosition = {\n sheetId: chart.keyValue.sheetId,\n col: chart.keyValue.zone.left,\n row: chart.keyValue.zone.top,\n };\n keyValueCell = getters.getEvaluatedCell(keyValuePosition);\n keyValue = String(keyValueCell.value);\n formattedKeyValue = keyValueCell.formattedValue;\n }\n let baselineCell;\n const baseline = chart.baseline;\n if (baseline) {\n const baselinePosition = {\n sheetId: chart.baseline.sheetId,\n col: chart.baseline.zone.left,\n row: chart.baseline.zone.top,\n };\n baselineCell = getters.getEvaluatedCell(baselinePosition);\n }\n const background = getters.getBackgroundOfSingleCellChart(chart.background, chart.keyValue);\n return {\n title: _t(chart.title),\n keyValue: formattedKeyValue || keyValue,\n baselineDisplay: getBaselineText(baselineCell, keyValueCell, chart.baselineMode),\n baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),\n baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),\n baselineDescr: chart.baselineDescr ? _t(chart.baselineDescr) : \"\",\n fontColor: chartFontColor(background),\n background,\n baselineStyle: chart.baselineMode !== \"percentage\" && baseline\n ? getters.getCellStyle({\n sheetId: baseline.sheetId,\n col: baseline.zone.left,\n row: baseline.zone.top,\n })\n : undefined,\n keyValueStyle: chart.keyValue\n ? getters.getCellStyle({\n sheetId: chart.keyValue.sheetId,\n col: chart.keyValue.zone.left,\n row: chart.keyValue.zone.top,\n })\n : undefined,\n };\n }\n\n /**\n * Registry\n *\n * The Registry class is basically just a mapping from a string key to an object.\n * It is really not much more than an object. It is however useful for the\n * following reasons:\n *\n * 1. it let us react and execute code when someone add something to the registry\n * (for example, the FunctionRegistry subclass this for this purpose)\n * 2. it throws an error when the get operation fails\n * 3. it provides a chained API to add items to the registry.\n */\n class Registry {\n constructor() {\n this.content = {};\n }\n /**\n * Add an item to the registry\n *\n * Note that this also returns the registry, so another add method call can\n * be chained\n */\n add(key, value) {\n this.content[key] = value;\n return this;\n }\n /**\n * Get an item from the registry\n */\n get(key) {\n /**\n * Note: key in {} is ~12 times slower than {}[key].\n * So, we check the absence of key only when the direct access returns\n * a falsy value. It's done to ensure that the registry can contains falsy values\n */\n const content = this.content[key];\n if (!content) {\n if (!(key in this.content)) {\n throw new Error(`Cannot find ${key} in this registry!`);\n }\n }\n return content;\n }\n /**\n * Check if the key is already in the registry\n */\n contains(key) {\n return key in this.content;\n }\n /**\n * Get a list of all elements in the registry\n */\n getAll() {\n return Object.values(this.content);\n }\n /**\n * Get a list of all keys in the registry\n */\n getKeys() {\n return Object.keys(this.content);\n }\n /**\n * Remove an item from the registry\n */\n remove(key) {\n delete this.content[key];\n }\n }\n\n /**\n * This registry is intended to map a cell content (raw string) to\n * an instance of a cell.\n */\n const chartRegistry = new Registry();\n chartRegistry.add(\"bar\", {\n match: (type) => type === \"bar\",\n createChart: (definition, sheetId, getters) => new BarChart(definition, sheetId, getters),\n getChartRuntime: createBarChartRuntime,\n validateChartDefinition: (validator, definition) => BarChart.validateChartDefinition(validator, definition),\n transformDefinition: (definition, executed) => BarChart.transformDefinition(definition, executed),\n getChartDefinitionFromContextCreation: (context) => BarChart.getDefinitionFromContextCreation(context),\n name: _lt(\"Bar\"),\n });\n chartRegistry.add(\"line\", {\n match: (type) => type === \"line\",\n createChart: (definition, sheetId, getters) => new LineChart(definition, sheetId, getters),\n getChartRuntime: createLineChartRuntime,\n validateChartDefinition: (validator, definition) => LineChart.validateChartDefinition(validator, definition),\n transformDefinition: (definition, executed) => LineChart.transformDefinition(definition, executed),\n getChartDefinitionFromContextCreation: (context) => LineChart.getDefinitionFromContextCreation(context),\n name: _lt(\"Line\"),\n });\n chartRegistry.add(\"pie\", {\n match: (type) => type === \"pie\",\n createChart: (definition, sheetId, getters) => new PieChart(definition, sheetId, getters),\n getChartRuntime: createPieChartRuntime,\n validateChartDefinition: (validator, definition) => PieChart.validateChartDefinition(validator, definition),\n transformDefinition: (definition, executed) => PieChart.transformDefinition(definition, executed),\n getChartDefinitionFromContextCreation: (context) => PieChart.getDefinitionFromContextCreation(context),\n name: _lt(\"Pie\"),\n });\n chartRegistry.add(\"scorecard\", {\n match: (type) => type === \"scorecard\",\n createChart: (definition, sheetId, getters) => new ScorecardChart(definition, sheetId, getters),\n getChartRuntime: createScorecardChartRuntime,\n validateChartDefinition: (validator, definition) => ScorecardChart.validateChartDefinition(validator, definition),\n transformDefinition: (definition, executed) => ScorecardChart.transformDefinition(definition, executed),\n getChartDefinitionFromContextCreation: (context) => ScorecardChart.getDefinitionFromContextCreation(context),\n name: _lt(\"Scorecard\"),\n });\n chartRegistry.add(\"gauge\", {\n match: (type) => type === \"gauge\",\n createChart: (definition, sheetId, getters) => new GaugeChart(definition, sheetId, getters),\n getChartRuntime: createGaugeChartRuntime,\n validateChartDefinition: (validator, definition) => GaugeChart.validateChartDefinition(validator, definition),\n transformDefinition: (definition, executed) => GaugeChart.transformDefinition(definition, executed),\n getChartDefinitionFromContextCreation: (context) => GaugeChart.getDefinitionFromContextCreation(context),\n name: _lt(\"Gauge\"),\n });\n const chartComponentRegistry = new Registry();\n chartComponentRegistry.add(\"line\", ChartJsComponent);\n chartComponentRegistry.add(\"bar\", ChartJsComponent);\n chartComponentRegistry.add(\"pie\", ChartJsComponent);\n chartComponentRegistry.add(\"gauge\", ChartJsComponent);\n chartComponentRegistry.add(\"scorecard\", ScorecardChart$1);\n\n const DEFAULT_MENU_ITEM = (key) => ({\n isVisible: () => true,\n isEnabled: () => true,\n isReadonlyAllowed: false,\n description: \"\",\n action: false,\n children: [],\n separator: false,\n icon: false,\n id: key,\n });\n function createFullMenuItem(key, value) {\n return Object.assign({}, DEFAULT_MENU_ITEM(key), value);\n }\n function isMenuItem(value) {\n return typeof value !== \"function\";\n }\n /**\n * The class Registry is extended in order to add the function addChild\n *\n */\n class MenuItemRegistry extends Registry {\n /**\n * @override\n */\n add(key, value) {\n this.content[key] = createFullMenuItem(key, value);\n return this;\n }\n /**\n * Add a subitem to an existing item\n * @param path Path of items to add this subitem\n * @param value Subitem to add\n */\n addChild(key, path, value) {\n const root = path.splice(0, 1)[0];\n let node = this.content[root];\n if (!node) {\n throw new Error(`Path ${root + \":\" + path.join(\":\")} not found`);\n }\n for (let p of path) {\n node = node.children.filter(isMenuItem).find((elt) => elt.id === p);\n if (!node) {\n throw new Error(`Path ${root + \":\" + path.join(\":\")} not found`);\n }\n }\n if (typeof value !== \"function\") {\n node.children.push(createFullMenuItem(key, value));\n }\n else {\n node.children.push(value);\n }\n return this;\n }\n /**\n * Get a list of all elements in the registry, ordered by sequence\n * @override\n */\n getAll() {\n return super.getAll().sort((a, b) => a.sequence - b.sequence);\n }\n }\n\n /**\n * Return the o-spreadsheet element position relative\n * to the browser viewport.\n */\n function useSpreadsheetRect() {\n const position = owl.useState({ x: 0, y: 0, width: 0, height: 0 });\n let spreadsheetElement = document.querySelector(\".o-spreadsheet\");\n updatePosition();\n function updatePosition() {\n if (!spreadsheetElement) {\n spreadsheetElement = document.querySelector(\".o-spreadsheet\");\n }\n if (spreadsheetElement) {\n const { top, left, width, height } = spreadsheetElement.getBoundingClientRect();\n position.x = left;\n position.y = top;\n position.width = width;\n position.height = height;\n }\n }\n owl.onMounted(updatePosition);\n owl.onPatched(updatePosition);\n return position;\n }\n /**\n * Return the component (or ref's component) BoundingRect, relative\n * to the upper left corner of the screen ( element).\n *\n * Note: when used with a component, it will\n * return the portal position, not the teleported position.\n */\n function useAbsoluteBoundingRect(ref) {\n const rect = owl.useState({ x: 0, y: 0, width: 0, height: 0 });\n function updateElRect() {\n const el = ref.el;\n if (el === null) {\n return;\n }\n const { top, left, width, height } = el.getBoundingClientRect();\n rect.x = left;\n rect.y = top;\n rect.width = width;\n rect.height = height;\n }\n owl.onMounted(updateElRect);\n owl.onPatched(updateElRect);\n return rect;\n }\n /**\n * Get the rectangle inside which a popover should stay when being displayed.\n * It's the value defined in `env.getPopoverContainerRect`, or the Rect of the \"o-spreadsheet\"\n * element by default.\n *\n * Coordinates are expressed expressed as absolute DOM position.\n */\n function usePopoverContainer() {\n const container = owl.useState({ x: 0, y: 0, width: 0, height: 0 });\n const component = owl.useComponent();\n const spreadsheetRect = useSpreadsheetRect();\n function updateRect() {\n const env = component.env;\n const newRect = \"getPopoverContainerRect\" in env ? env.getPopoverContainerRect() : spreadsheetRect;\n container.x = newRect.x;\n container.y = newRect.y;\n container.width = newRect.width;\n container.height = newRect.height;\n }\n updateRect();\n owl.onMounted(updateRect);\n owl.onPatched(updateRect);\n return container;\n }\n\n function getMenuChildren(node, env) {\n const children = [];\n for (const child of node.children) {\n if (typeof child === \"function\") {\n children.push(...child(env));\n }\n else {\n children.push(child);\n }\n }\n return children.sort((a, b) => a.sequence - b.sequence);\n }\n function getMenuName(node, env) {\n if (typeof node.name === \"function\") {\n return node.name(env);\n }\n return node.name;\n }\n function getMenuDescription(node) {\n return node.description ? node.description : \"\";\n }\n\n /**\n * Return true if the event was triggered from\n * a child element.\n */\n function isChildEvent(parent, ev) {\n return !!ev.target && parent.contains(ev.target);\n }\n function gridOverlayPosition() {\n const spreadsheetElement = document.querySelector(\".o-grid-overlay\");\n if (spreadsheetElement) {\n const { top, left } = spreadsheetElement === null || spreadsheetElement === void 0 ? void 0 : spreadsheetElement.getBoundingClientRect();\n return { top, left };\n }\n throw new Error(\"Can't find spreadsheet position\");\n }\n function getOpenedMenus() {\n return Array.from(document.querySelectorAll(\".o-spreadsheet .o-menu\"));\n }\n\n /**\n * Compute the intersection of two rectangles. Returns nothing if the two rectangles don't overlap\n */\n function rectIntersection(rect1, rect2) {\n return zoneToRect(intersection(rectToZone(rect1), rectToZone(rect2)));\n }\n function rectToZone(rect) {\n return {\n left: rect.x,\n top: rect.y,\n right: rect.x + rect.width,\n bottom: rect.y + rect.height,\n };\n }\n function zoneToRect(zone) {\n if (!zone)\n return undefined;\n return {\n x: zone.left,\n y: zone.top,\n width: zone.right - zone.left,\n height: zone.bottom - zone.top,\n };\n }\n\n css /* scss */ `\n .o-popover {\n position: absolute;\n z-index: ${ComponentsImportance.Popover};\n overflow: auto;\n box-shadow: 1px 2px 5px 2px rgb(51 51 51 / 15%);\n width: fit-content;\n height: fit-content;\n }\n`;\n class Popover extends owl.Component {\n constructor() {\n super(...arguments);\n this.popoverRef = owl.useRef(\"popover\");\n this.currentPosition = undefined;\n this.currentDisplayValue = undefined;\n this.spreadsheetRect = useSpreadsheetRect();\n }\n setup() {\n this.containerRect = usePopoverContainer();\n // useEffect occurs after the DOM is created and the element width/height are computed, but before\n // the element in rendered, so we can still set its position\n owl.useEffect(() => {\n var _a, _b, _c, _d;\n if (!this.containerRect)\n throw new Error(\"Popover container is not defined\");\n const el = this.popoverRef.el;\n const anchor = rectIntersection(this.props.anchorRect, this.containerRect);\n const newDisplay = anchor ? \"block\" : \"none\";\n if (this.currentDisplayValue !== \"none\" && newDisplay === \"none\") {\n (_b = (_a = this.props).onPopoverHidden) === null || _b === void 0 ? void 0 : _b.call(_a);\n }\n el.style.display = newDisplay;\n this.currentDisplayValue = newDisplay;\n if (!anchor)\n return;\n const propsMaxSize = { width: this.props.maxWidth, height: this.props.maxHeight };\n const elDims = {\n width: el.getBoundingClientRect().width,\n height: el.getBoundingClientRect().height,\n };\n const spreadsheetRect = this.spreadsheetRect;\n const popoverPositionHelper = this.props.positioning === \"BottomLeft\"\n ? new BottomLeftPopoverContext(anchor, this.containerRect, propsMaxSize, spreadsheetRect)\n : new TopRightPopoverContext(anchor, this.containerRect, propsMaxSize, spreadsheetRect);\n const style = popoverPositionHelper.getCss(elDims, this.props.verticalOffset);\n for (const property of Object.keys(style)) {\n el.style[property] = style[property];\n }\n const newPosition = popoverPositionHelper.getCurrentPosition(elDims);\n if (this.currentPosition && newPosition !== this.currentPosition) {\n (_d = (_c = this.props).onPopoverMoved) === null || _d === void 0 ? void 0 : _d.call(_c);\n }\n this.currentPosition = newPosition;\n });\n }\n }\n Popover.template = \"o-spreadsheet-Popover\";\n Popover.defaultProps = {\n positioning: \"BottomLeft\",\n verticalOffset: 0,\n onMouseWheel: () => { },\n onPopoverMoved: () => { },\n onPopoverHidden: () => { },\n };\n Popover.props = {\n anchorRect: Object,\n containerRect: { type: Object, optional: true },\n positioning: { type: String, optional: true },\n maxWidth: { type: Number, optional: true },\n maxHeight: { type: Number, optional: true },\n verticalOffset: { type: Number, optional: true },\n onMouseWheel: { type: Function, optional: true },\n onPopoverHidden: { type: Function, optional: true },\n onPopoverMoved: { type: Function, optional: true },\n slots: Object,\n };\n class PopoverPositionContext {\n constructor(anchorRect, containerRect, propsMaxSize, spreadsheetOffset) {\n this.anchorRect = anchorRect;\n this.containerRect = containerRect;\n this.propsMaxSize = propsMaxSize;\n this.spreadsheetOffset = spreadsheetOffset;\n }\n /** Check if there is enough space for the popover to be rendered at the bottom of the anchorRect */\n shouldRenderAtBottom(elementHeight) {\n return (elementHeight <= this.availableHeightDown ||\n this.availableHeightDown >= this.availableHeightUp);\n }\n /** Check if there is enough space for the popover to be rendered at the right of the anchorRect */\n shouldRenderAtRight(elementWidth) {\n return (elementWidth <= this.availableWidthRight ||\n this.availableWidthRight >= this.availableWidthLeft);\n }\n getMaxHeight(elementHeight) {\n const shouldRenderAtBottom = this.shouldRenderAtBottom(elementHeight);\n const availableHeight = shouldRenderAtBottom\n ? this.availableHeightDown\n : this.availableHeightUp;\n return this.propsMaxSize.height\n ? Math.min(availableHeight, this.propsMaxSize.height)\n : availableHeight;\n }\n getMaxWidth(elementWidth) {\n const shouldRenderAtRight = this.shouldRenderAtRight(elementWidth);\n const availableWidth = shouldRenderAtRight ? this.availableWidthRight : this.availableWidthLeft;\n return this.propsMaxSize.width\n ? Math.min(availableWidth, this.propsMaxSize.width)\n : availableWidth;\n }\n getCss(elDims, verticalOffset) {\n const maxHeight = this.getMaxHeight(elDims.height);\n const maxWidth = this.getMaxWidth(elDims.width);\n const actualHeight = Math.min(maxHeight, elDims.height);\n const actualWidth = Math.min(maxWidth, elDims.width);\n const shouldRenderAtBottom = this.shouldRenderAtBottom(elDims.height);\n const shouldRenderAtRight = this.shouldRenderAtRight(elDims.width);\n verticalOffset = shouldRenderAtBottom ? verticalOffset : -verticalOffset;\n const cssProperties = {\n \"max-height\": maxHeight + \"px\",\n \"max-width\": maxWidth + \"px\",\n top: this.getTopCoordinate(actualHeight, shouldRenderAtBottom) -\n this.spreadsheetOffset.y -\n verticalOffset +\n \"px\",\n left: this.getLeftCoordinate(actualWidth, shouldRenderAtRight) - this.spreadsheetOffset.x + \"px\",\n };\n return cssProperties;\n }\n getCurrentPosition(elDims) {\n const shouldRenderAtBottom = this.shouldRenderAtBottom(elDims.height);\n const shouldRenderAtRight = this.shouldRenderAtRight(elDims.width);\n if (shouldRenderAtBottom && shouldRenderAtRight)\n return \"BottomRight\";\n if (shouldRenderAtBottom && !shouldRenderAtRight)\n return \"BottomLeft\";\n if (!shouldRenderAtBottom && shouldRenderAtRight)\n return \"TopRight\";\n return \"TopLeft\";\n }\n }\n class BottomLeftPopoverContext extends PopoverPositionContext {\n get availableHeightUp() {\n return this.anchorRect.y - this.containerRect.y;\n }\n get availableHeightDown() {\n return this.containerRect.height - this.availableHeightUp - this.anchorRect.height;\n }\n get availableWidthRight() {\n return this.containerRect.x + this.containerRect.width - this.anchorRect.x;\n }\n get availableWidthLeft() {\n return this.anchorRect.x + this.anchorRect.width - this.containerRect.x;\n }\n getTopCoordinate(elementHeight, shouldRenderAtBottom) {\n if (shouldRenderAtBottom) {\n return this.anchorRect.y + this.anchorRect.height;\n }\n else {\n return this.anchorRect.y - elementHeight;\n }\n }\n getLeftCoordinate(elementWidth, shouldRenderAtRight) {\n if (shouldRenderAtRight) {\n return this.anchorRect.x;\n }\n else {\n return this.anchorRect.x + this.anchorRect.width - elementWidth;\n }\n }\n }\n class TopRightPopoverContext extends PopoverPositionContext {\n get availableHeightUp() {\n return this.anchorRect.y + this.anchorRect.height - this.containerRect.y;\n }\n get availableHeightDown() {\n return this.containerRect.y + this.containerRect.height - this.anchorRect.y;\n }\n get availableWidthRight() {\n return this.containerRect.width - this.anchorRect.width - this.availableWidthLeft;\n }\n get availableWidthLeft() {\n return this.anchorRect.x - this.containerRect.x;\n }\n getTopCoordinate(elementHeight, shouldRenderAtBottom) {\n if (shouldRenderAtBottom) {\n return this.anchorRect.y;\n }\n else {\n return this.anchorRect.y + this.anchorRect.height - elementHeight;\n }\n }\n getLeftCoordinate(elementWidth, shouldRenderAtRight) {\n if (shouldRenderAtRight) {\n return this.anchorRect.x + this.anchorRect.width;\n }\n else {\n return this.anchorRect.x - elementWidth;\n }\n }\n }\n\n //------------------------------------------------------------------------------\n // Context Menu Component\n //------------------------------------------------------------------------------\n css /* scss */ `\n .o-menu {\n background-color: white;\n padding: ${MENU_VERTICAL_PADDING}px 0px;\n width: ${MENU_WIDTH}px;\n box-sizing: border-box !important;\n\n .o-menu-item {\n display: flex;\n justify-content: space-between;\n align-items: center;\n box-sizing: border-box;\n height: ${MENU_ITEM_HEIGHT}px;\n padding: 4px 16px;\n cursor: pointer;\n user-select: none;\n\n .o-menu-item-name {\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n }\n\n &.o-menu-root {\n display: flex;\n justify-content: space-between;\n }\n .o-menu-item-icon {\n margin-top: auto;\n margin-bottom: auto;\n }\n .o-icon {\n width: 10px;\n }\n\n &:not(.disabled) {\n &:hover,\n &.o-menu-item-active {\n background-color: #ebebeb;\n }\n .o-menu-item-description {\n color: grey;\n }\n }\n &.disabled {\n color: ${MENU_ITEM_DISABLED_COLOR};\n cursor: not-allowed;\n }\n }\n }\n`;\n class Menu extends owl.Component {\n constructor() {\n super(...arguments);\n this.subMenu = owl.useState({\n isOpen: false,\n position: null,\n scrollOffset: 0,\n menuItems: [],\n });\n this.menuRef = owl.useRef(\"menu\");\n this.position = useAbsoluteBoundingRect(this.menuRef);\n }\n setup() {\n owl.useExternalListener(window, \"click\", this.onExternalClick, { capture: true });\n owl.useExternalListener(window, \"contextmenu\", this.onExternalClick, { capture: true });\n owl.onWillUpdateProps((nextProps) => {\n if (nextProps.menuItems !== this.props.menuItems) {\n this.closeSubMenu();\n }\n });\n }\n get subMenuPosition() {\n const position = Object.assign({}, this.subMenu.position);\n position.y -= this.subMenu.scrollOffset || 0;\n return position;\n }\n get menuHeight() {\n const menuItems = this.props.menuItems;\n let menuItemsHeight = this.getMenuItemsHeight(menuItems);\n // We don't display separator at the end of a menu\n if (menuItems[menuItems.length - 1].separator) {\n menuItemsHeight -= MENU_SEPARATOR_HEIGHT;\n }\n return 2 * MENU_VERTICAL_PADDING + menuItemsHeight;\n }\n get popoverProps() {\n const isRoot = this.props.depth === 1;\n return {\n anchorRect: {\n x: this.props.position.x - MENU_WIDTH * (this.props.depth - 1),\n y: this.props.position.y,\n width: isRoot ? 0 : MENU_WIDTH,\n height: isRoot ? 0 : MENU_ITEM_HEIGHT,\n },\n positioning: \"TopRight\",\n verticalOffset: isRoot ? 0 : MENU_VERTICAL_PADDING,\n onPopoverHidden: () => this.closeSubMenu(),\n onPopoverMoved: () => this.closeSubMenu(),\n maxHeight: this.menuHeight,\n };\n }\n getColor(menu) {\n return menu.textColor ? `color: ${menu.textColor}` : undefined;\n }\n async activateMenu(menu) {\n var _a, _b;\n const result = await menu.action(this.env);\n this.close();\n (_b = (_a = this.props).onMenuClicked) === null || _b === void 0 ? void 0 : _b.call(_a, { detail: result });\n }\n close() {\n this.closeSubMenu();\n this.props.onClose();\n }\n /**\n * Return the number of pixels between the top of the menu\n * and the menu item at a given index.\n */\n subMenuVerticalPosition(menuIndex) {\n const menusAbove = this.props.menuItems.slice(0, menuIndex);\n return this.position.y + this.getMenuItemsHeight(menusAbove) + MENU_VERTICAL_PADDING;\n }\n onExternalClick(ev) {\n // Don't close a root menu when clicked to open the submenus.\n const el = this.menuRef.el;\n if (el && getOpenedMenus().some((el) => isChildEvent(el, ev))) {\n return;\n }\n ev.closedMenuId = this.props.menuId;\n this.close();\n }\n getMenuItemsHeight(menuItems) {\n const numberOfSeparators = menuItems.filter((m) => m.separator).length;\n return MENU_ITEM_HEIGHT * menuItems.length + MENU_SEPARATOR_HEIGHT * numberOfSeparators;\n }\n getName(menu) {\n return getMenuName(menu, this.env);\n }\n getDescription(menu) {\n return getMenuDescription(menu);\n }\n isRoot(menu) {\n return !menu.action;\n }\n isEnabled(menu) {\n if (menu.isEnabled(this.env)) {\n return this.env.model.getters.isReadonly() ? menu.isReadonlyAllowed : true;\n }\n return false;\n }\n onScroll(ev) {\n this.subMenu.scrollOffset = ev.target.scrollTop;\n }\n /**\n * If the given menu is not disabled, open it's submenu at the\n * correct position according to available surrounding space.\n */\n openSubMenu(menu, menuIndex) {\n const y = this.subMenuVerticalPosition(menuIndex);\n this.subMenu.position = {\n x: this.position.x + this.props.depth * MENU_WIDTH,\n y: y - (this.subMenu.scrollOffset || 0),\n };\n this.subMenu.menuItems = getMenuChildren(menu, this.env).filter((item) => !item.isVisible || item.isVisible(this.env));\n this.subMenu.isOpen = true;\n this.subMenu.parentMenu = menu;\n }\n isParentMenu(subMenu, menuItem) {\n var _a;\n return ((_a = subMenu.parentMenu) === null || _a === void 0 ? void 0 : _a.id) === menuItem.id;\n }\n closeSubMenu() {\n this.subMenu.isOpen = false;\n this.subMenu.parentMenu = undefined;\n }\n onClickMenu(menu, menuIndex) {\n if (this.isEnabled(menu)) {\n if (this.isRoot(menu)) {\n this.openSubMenu(menu, menuIndex);\n }\n else {\n this.activateMenu(menu);\n }\n }\n }\n onMouseOver(menu, position) {\n if (menu.isEnabled(this.env)) {\n if (this.isRoot(menu)) {\n this.openSubMenu(menu, position);\n }\n else {\n this.closeSubMenu();\n }\n }\n }\n }\n Menu.template = \"o-spreadsheet-Menu\";\n Menu.components = { Menu, Popover };\n Menu.defaultProps = {\n depth: 1,\n };\n Menu.props = {\n position: Object,\n menuItems: Array,\n depth: { type: Number, optional: true },\n onClose: Function,\n onMenuClicked: { type: Function, optional: true },\n menuId: { type: String, optional: true },\n };\n\n // -----------------------------------------------------------------------------\n // STYLE\n // -----------------------------------------------------------------------------\n css /* scss */ `\n .o-chart-container {\n width: 100%;\n height: 100%;\n position: relative;\n }\n`;\n class ChartFigure extends owl.Component {\n constructor() {\n super(...arguments);\n this.menuState = owl.useState({ isOpen: false, position: null, menuItems: [] });\n this.chartContainerRef = owl.useRef(\"chartContainer\");\n this.menuButtonRef = owl.useRef(\"menuButton\");\n this.menuButtonRect = useAbsoluteBoundingRect(this.menuButtonRef);\n this.position = useAbsoluteBoundingRect(this.chartContainerRef);\n }\n getMenuItemRegistry() {\n const registry = new MenuItemRegistry();\n registry.add(\"edit\", {\n name: _lt(\"Edit\"),\n sequence: 1,\n action: () => {\n this.env.model.dispatch(\"SELECT_FIGURE\", { id: this.props.figure.id });\n this.env.openSidePanel(\"ChartPanel\");\n },\n });\n registry.add(\"copy\", {\n name: _lt(\"Copy\"),\n sequence: 2,\n action: async () => {\n this.env.model.dispatch(\"SELECT_FIGURE\", { id: this.props.figure.id });\n this.env.model.dispatch(\"COPY\");\n await this.env.clipboard.write(this.env.model.getters.getClipboardContent());\n },\n });\n registry.add(\"cut\", {\n name: _lt(\"Cut\"),\n sequence: 3,\n action: async () => {\n this.env.model.dispatch(\"SELECT_FIGURE\", { id: this.props.figure.id });\n this.env.model.dispatch(\"CUT\");\n await this.env.clipboard.write(this.env.model.getters.getClipboardContent());\n },\n });\n registry.add(\"delete\", {\n name: _lt(\"Delete\"),\n sequence: 10,\n action: () => {\n this.env.model.dispatch(\"DELETE_FIGURE\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n id: this.props.figure.id,\n });\n this.props.onFigureDeleted();\n },\n });\n return registry;\n }\n get chartType() {\n return this.env.model.getters.getChartType(this.props.figure.id);\n }\n onContextMenu(ev) {\n const position = {\n x: this.position.x + ev.offsetX,\n y: this.position.y + ev.offsetY,\n };\n this.openContextMenu(position);\n }\n showMenu() {\n const { x, y, width } = this.menuButtonRect;\n const menuPosition = {\n x: x >= MENU_WIDTH ? x - MENU_WIDTH : x + width,\n y: y,\n };\n this.openContextMenu(menuPosition);\n }\n openContextMenu(position) {\n const registry = this.getMenuItemRegistry();\n this.menuState.isOpen = true;\n this.menuState.menuItems = registry.getAll().filter((x) => x.isVisible(this.env));\n this.menuState.position = position;\n }\n get chartComponent() {\n const type = this.chartType;\n const component = chartComponentRegistry.get(type);\n if (!component) {\n throw new Error(`Component is not defined for type ${type}`);\n }\n return component;\n }\n }\n ChartFigure.template = \"o-spreadsheet-ChartFigure\";\n ChartFigure.components = { Menu };\n ChartFigure.props = {\n figure: Object,\n onFigureDeleted: Function,\n };\n\n function interactiveCut(env) {\n const result = env.model.dispatch(\"CUT\");\n if (!result.isSuccessful) {\n if (result.isCancelledBecause(19 /* CommandResult.WrongCutSelection */)) {\n env.raiseError(_lt(\"This operation is not allowed with multiple selections.\"));\n }\n }\n }\n\n const PasteInteractiveContent = {\n wrongPasteSelection: _lt(\"This operation is not allowed with multiple selections.\"),\n willRemoveExistingMerge: _lt(\"This operation is not possible due to a merge. Please remove the merges first than try again.\"),\n wrongFigurePasteOption: _lt(\"Cannot do a special paste of a figure.\"),\n frozenPaneOverlap: _lt(\"Cannot paste merged cells over a frozen pane.\"),\n };\n function handlePasteResult(env, result) {\n if (!result.isSuccessful) {\n if (result.reasons.includes(20 /* CommandResult.WrongPasteSelection */)) {\n env.raiseError(PasteInteractiveContent.wrongPasteSelection);\n }\n else if (result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {\n env.raiseError(PasteInteractiveContent.willRemoveExistingMerge);\n }\n else if (result.reasons.includes(22 /* CommandResult.WrongFigurePasteOption */)) {\n env.raiseError(PasteInteractiveContent.wrongFigurePasteOption);\n }\n else if (result.reasons.includes(75 /* CommandResult.FrozenPaneOverlap */)) {\n env.raiseError(PasteInteractiveContent.frozenPaneOverlap);\n }\n }\n }\n function interactivePaste(env, target, pasteOption) {\n const result = env.model.dispatch(\"PASTE\", { target, pasteOption });\n handlePasteResult(env, result);\n }\n function interactivePasteFromOS(env, target, text) {\n const result = env.model.dispatch(\"PASTE_FROM_OS_CLIPBOARD\", { target, text });\n handlePasteResult(env, result);\n }\n\n /**\n * Create a function used to create a Chart based on the definition\n */\n function chartFactory(getters) {\n const builders = chartRegistry.getAll();\n function createChart(id, definition, sheetId) {\n const builder = builders.find((builder) => builder.match(definition.type));\n if (!builder) {\n throw new Error(`No builder for this chart: ${definition.type}`);\n }\n return builder.createChart(definition, sheetId, getters);\n }\n return createChart;\n }\n /**\n * Create a function used to create a Chart Runtime based on the chart class\n * instance\n */\n function chartRuntimeFactory(getters) {\n const builders = chartRegistry.getAll();\n function createRuntimeChart(chart) {\n const builder = builders.find((builder) => builder.match(chart.type));\n if (!builder) {\n throw new Error(\"No runtime builder for this chart.\");\n }\n return builder.getChartRuntime(chart, getters);\n }\n return createRuntimeChart;\n }\n /**\n * Validate the chart definition given in arguments\n */\n function validateChartDefinition(validator, definition) {\n const validators = chartRegistry.getAll().find((validator) => validator.match(definition.type));\n if (!validators) {\n throw new Error(\"Unknown chart type.\");\n }\n return validators.validateChartDefinition(validator, definition);\n }\n /**\n * Get a new chart definition transformed with the executed command. This\n * functions will be called during operational transform process\n */\n function transformDefinition(definition, executed) {\n const transformation = chartRegistry.getAll().find((factory) => factory.match(definition.type));\n if (!transformation) {\n throw new Error(\"Unknown chart type.\");\n }\n return transformation.transformDefinition(definition, executed);\n }\n /**\n * Get an empty definition based on the given context and the given type\n */\n function getChartDefinitionFromContextCreation(context, type) {\n const chartClass = chartRegistry.get(type);\n return chartClass.getChartDefinitionFromContextCreation(context);\n }\n function getChartTypes() {\n const result = {};\n for (const key of chartRegistry.getKeys()) {\n result[key] = chartRegistry.get(key).name;\n }\n return result;\n }\n /**\n * Return a \"smart\" chart definition in the given zone. The definition is \"smart\" because it will\n * use the best type of chart to display the data of the zone.\n *\n * It will also try to find labels and datasets in the range, and try to find title for the datasets.\n *\n * The type of chart will be :\n * - If the zone is a single non-empty cell, returns a scorecard\n * - If the all the labels are numbers/date, returns a line chart\n * - Else returns a bar chart\n */\n function getSmartChartDefinition(zone, getters) {\n var _a;\n let dataSetZone = zone;\n if (zone.left !== zone.right) {\n dataSetZone = { ...zone, left: zone.left + 1 };\n }\n const dataSets = [zoneToXc(dataSetZone)];\n const sheetId = getters.getActiveSheetId();\n const topLeftCell = getters.getCell({ sheetId, col: zone.left, row: zone.top });\n if (getZoneArea(zone) === 1 && (topLeftCell === null || topLeftCell === void 0 ? void 0 : topLeftCell.content)) {\n return {\n type: \"scorecard\",\n title: \"\",\n background: ((_a = topLeftCell.style) === null || _a === void 0 ? void 0 : _a.fillColor) || undefined,\n keyValue: zoneToXc(zone),\n baselineMode: DEFAULT_SCORECARD_BASELINE_MODE,\n baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,\n baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,\n };\n }\n let title = \"\";\n const cellsInFirstRow = getters.getEvaluatedCellsInZone(sheetId, {\n ...dataSetZone,\n bottom: dataSetZone.top,\n });\n const dataSetsHaveTitle = !!cellsInFirstRow.find((cell) => cell.type !== CellValueType.empty && cell.type !== CellValueType.number);\n if (dataSetsHaveTitle) {\n const texts = cellsInFirstRow\n .filter((cell) => cell.type !== CellValueType.error && cell.type !== CellValueType.empty)\n .map((cell) => cell.formattedValue);\n const lastElement = texts.splice(-1)[0];\n title = texts.join(\", \");\n if (lastElement) {\n title += (title ? \" \" + _t(\"and\") + \" \" : \"\") + lastElement;\n }\n }\n let labelRangeXc;\n if (zone.left !== zone.right) {\n labelRangeXc = zoneToXc({\n ...zone,\n right: zone.left,\n top: dataSetsHaveTitle ? zone.top + 1 : zone.top,\n });\n }\n // Only display legend for several datasets.\n const newLegendPos = dataSetZone.right === dataSetZone.left ? \"none\" : \"top\";\n const labelRange = labelRangeXc ? getters.getRangeFromSheetXC(sheetId, labelRangeXc) : undefined;\n if (canChartParseLabels(labelRange, getters)) {\n return {\n title,\n dataSets,\n labelsAsText: false,\n stacked: false,\n aggregated: false,\n labelRange: labelRangeXc,\n type: \"line\",\n dataSetsHaveTitle,\n verticalAxisPosition: \"left\",\n legendPosition: newLegendPos,\n };\n }\n return {\n title,\n dataSets,\n labelRange: labelRangeXc,\n type: \"bar\",\n stacked: false,\n aggregated: false,\n dataSetsHaveTitle,\n verticalAxisPosition: \"left\",\n legendPosition: newLegendPos,\n };\n }\n\n function centerFigurePosition(getters, size) {\n const { x: offsetCorrectionX, y: offsetCorrectionY } = getters.getMainViewportCoordinates();\n const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();\n const dim = getters.getSheetViewDimension();\n const rect = getters.getVisibleRect(getters.getActiveMainViewport());\n const scrollableViewportWidth = Math.min(rect.width, dim.width - offsetCorrectionX);\n const scrollableViewportHeight = Math.min(rect.height, dim.height - offsetCorrectionY);\n const position = {\n x: offsetCorrectionX + scrollX + Math.max(0, (scrollableViewportWidth - size.width) / 2),\n y: offsetCorrectionY + scrollY + Math.max(0, (scrollableViewportHeight - size.height) / 2),\n }; // Position at the center of the scrollable viewport\n return position;\n }\n function getMaxFigureSize(getters, figureSize) {\n const size = deepCopy(figureSize);\n const dim = getters.getSheetViewDimension();\n const maxWidth = dim.width;\n const maxHeight = dim.height;\n if (size.width > maxWidth) {\n const ratio = maxWidth / size.width;\n size.width = maxWidth;\n size.height = size.height * ratio;\n }\n if (size.height > maxHeight) {\n const ratio = maxHeight / size.height;\n size.height = maxHeight;\n size.width = size.width * ratio;\n }\n return size;\n }\n\n const SORT_TYPES = [\n CellValueType.number,\n CellValueType.error,\n CellValueType.text,\n CellValueType.boolean,\n ];\n function sortCells(cells, sortDirection, emptyCellAsZero) {\n const cellsWithIndex = cells.map((cell, index) => ({\n index,\n type: cell.type,\n value: cell.value,\n }));\n let emptyCells = cellsWithIndex.filter((x) => x.type === CellValueType.empty);\n let nonEmptyCells = cellsWithIndex.filter((x) => x.type !== CellValueType.empty);\n if (emptyCellAsZero) {\n nonEmptyCells.push(...emptyCells.map((emptyCell) => ({ ...emptyCell, type: CellValueType.number, value: 0 })));\n emptyCells = [];\n }\n const inverse = sortDirection === \"descending\" ? -1 : 1;\n return nonEmptyCells\n .sort((left, right) => {\n let typeOrder = SORT_TYPES.indexOf(left.type) - SORT_TYPES.indexOf(right.type);\n if (typeOrder === 0) {\n if (left.type === CellValueType.text || left.type === CellValueType.error) {\n typeOrder = left.value.localeCompare(right.value);\n }\n else\n typeOrder = left.value - right.value;\n }\n return inverse * typeOrder;\n })\n .concat(emptyCells);\n }\n function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {\n let result = DispatchResult.Success;\n //several columns => bypass the contiguity check\n let multiColumns = zone.right > zone.left;\n if (env.model.getters.doesIntersectMerge(sheetId, zone)) {\n multiColumns = false;\n let table;\n for (let row = zone.top; row <= zone.bottom; row++) {\n table = [];\n for (let col = zone.left; col <= zone.right; col++) {\n let merge = env.model.getters.getMerge({ sheetId, col, row });\n if (merge && !table.includes(merge.id.toString())) {\n table.push(merge.id.toString());\n }\n }\n if (table.length >= 2) {\n multiColumns = true;\n break;\n }\n }\n }\n const { col, row } = anchor;\n if (multiColumns) {\n result = env.model.dispatch(\"SORT_CELLS\", { sheetId, col, row, zone, sortDirection });\n }\n else {\n // check contiguity\n const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);\n if (isEqual(contiguousZone, zone)) {\n // merge as it is\n result = env.model.dispatch(\"SORT_CELLS\", {\n sheetId,\n col,\n row,\n zone,\n sortDirection,\n });\n }\n else {\n env.askConfirmation(_lt(\"We found data next to your selection. Since this data was not selected, it will not be sorted. Do you want to extend your selection?\"), () => {\n zone = contiguousZone;\n result = env.model.dispatch(\"SORT_CELLS\", {\n sheetId,\n col,\n row,\n zone,\n sortDirection,\n });\n }, () => {\n result = env.model.dispatch(\"SORT_CELLS\", {\n sheetId,\n col,\n row,\n zone,\n sortDirection,\n });\n });\n }\n }\n if (result.isCancelledBecause(63 /* CommandResult.InvalidSortZone */)) {\n const { col, row } = anchor;\n env.model.selection.selectZone({ cell: { col, row }, zone });\n env.raiseError(_lt(\"Cannot sort. To sort, select only cells or only merges that have the same size.\"));\n }\n }\n\n const AddFilterInteractiveContent = {\n filterOverlap: _lt(\"You cannot create overlapping filters.\"),\n nonContinuousTargets: _lt(\"A filter can only be created on a continuous selection.\"),\n mergeInFilter: _lt(\"You can't create a filter over a range that contains a merge.\"),\n };\n function interactiveAddFilter(env, sheetId, target) {\n const result = env.model.dispatch(\"CREATE_FILTER_TABLE\", { target, sheetId });\n if (result.isCancelledBecause(78 /* CommandResult.FilterOverlap */)) {\n env.raiseError(AddFilterInteractiveContent.filterOverlap);\n }\n else if (result.isCancelledBecause(80 /* CommandResult.MergeInFilter */)) {\n env.raiseError(AddFilterInteractiveContent.mergeInFilter);\n }\n else if (result.isCancelledBecause(81 /* CommandResult.NonContinuousTargets */)) {\n env.raiseError(AddFilterInteractiveContent.nonContinuousTargets);\n }\n }\n\n //------------------------------------------------------------------------------\n // Helpers\n //------------------------------------------------------------------------------\n function getColumnsNumber(env) {\n const activeCols = env.model.getters.getActiveCols();\n if (activeCols.size) {\n return activeCols.size;\n }\n else {\n const zone = env.model.getters.getSelectedZones()[0];\n return zone.right - zone.left + 1;\n }\n }\n function getRowsNumber(env) {\n const activeRows = env.model.getters.getActiveRows();\n if (activeRows.size) {\n return activeRows.size;\n }\n else {\n const zone = env.model.getters.getSelectedZones()[0];\n return zone.bottom - zone.top + 1;\n }\n }\n function setFormatter(env, format) {\n env.model.dispatch(\"SET_FORMATTING\", {\n sheetId: env.model.getters.getActiveSheetId(),\n target: env.model.getters.getSelectedZones(),\n format,\n });\n }\n function setStyle(env, style) {\n env.model.dispatch(\"SET_FORMATTING\", {\n sheetId: env.model.getters.getActiveSheetId(),\n target: env.model.getters.getSelectedZones(),\n style,\n });\n }\n //------------------------------------------------------------------------------\n // Simple actions\n //------------------------------------------------------------------------------\n const UNDO_ACTION = (env) => env.model.dispatch(\"REQUEST_UNDO\");\n const REDO_ACTION = (env) => env.model.dispatch(\"REQUEST_REDO\");\n const COPY_ACTION = async (env) => {\n env.model.dispatch(\"COPY\");\n await env.clipboard.write(env.model.getters.getClipboardContent());\n };\n const CUT_ACTION = async (env) => {\n interactiveCut(env);\n await env.clipboard.write(env.model.getters.getClipboardContent());\n };\n const PASTE_ACTION = async (env) => paste(env);\n const PASTE_VALUE_ACTION = async (env) => paste(env, \"onlyValue\");\n async function paste(env, pasteOption) {\n const spreadsheetClipboard = env.model.getters.getClipboardTextContent();\n const osClipboard = await env.clipboard.readText();\n switch (osClipboard.status) {\n case \"ok\":\n const target = env.model.getters.getSelectedZones();\n if (osClipboard && osClipboard.content !== spreadsheetClipboard) {\n interactivePasteFromOS(env, target, osClipboard.content);\n }\n else {\n interactivePaste(env, target, pasteOption);\n }\n break;\n case \"notImplemented\":\n env.raiseError(_lt(\"Pasting from the context menu is not supported in this browser. Use keyboard shortcuts ctrl+c / ctrl+v instead.\"));\n break;\n case \"permissionDenied\":\n env.raiseError(_lt(\"Access to the clipboard denied by the browser. Please enable clipboard permission for this page in your browser settings.\"));\n break;\n }\n }\n const PASTE_FORMAT_ACTION = (env) => interactivePaste(env, env.model.getters.getSelectedZones(), \"onlyFormat\");\n const DELETE_CONTENT_ACTION = (env) => env.model.dispatch(\"DELETE_CONTENT\", {\n sheetId: env.model.getters.getActiveSheetId(),\n target: env.model.getters.getSelectedZones(),\n });\n const SET_FORMULA_VISIBILITY_ACTION = (env) => env.model.dispatch(\"SET_FORMULA_VISIBILITY\", { show: !env.model.getters.shouldShowFormulas() });\n const SET_GRID_LINES_VISIBILITY_ACTION = (env) => {\n const sheetId = env.model.getters.getActiveSheetId();\n env.model.dispatch(\"SET_GRID_LINES_VISIBILITY\", {\n sheetId,\n areGridLinesVisible: !env.model.getters.getGridLinesVisibility(sheetId),\n });\n };\n const IS_NOT_CUT_OPERATION = (env) => {\n return !env.model.getters.isCutOperation();\n };\n //------------------------------------------------------------------------------\n // Grid manipulations\n //------------------------------------------------------------------------------\n const DELETE_CONTENT_ROWS_NAME = (env) => {\n if (env.model.getters.getSelectedZones().length > 1) {\n return _lt(\"Clear rows\");\n }\n let first;\n let last;\n const activesRows = env.model.getters.getActiveRows();\n if (activesRows.size !== 0) {\n first = Math.min(...activesRows);\n last = Math.max(...activesRows);\n }\n else {\n const zone = env.model.getters.getSelectedZones()[0];\n first = zone.top;\n last = zone.bottom;\n }\n if (first === last) {\n return _lt(\"Clear row %s\", (first + 1).toString());\n }\n return _lt(\"Clear rows %s - %s\", (first + 1).toString(), (last + 1).toString());\n };\n const DELETE_CONTENT_ROWS_ACTION = (env) => {\n const sheetId = env.model.getters.getActiveSheetId();\n const target = [...env.model.getters.getActiveRows()].map((index) => env.model.getters.getRowsZone(sheetId, index, index));\n env.model.dispatch(\"DELETE_CONTENT\", {\n target,\n sheetId: env.model.getters.getActiveSheetId(),\n });\n };\n const DELETE_CONTENT_COLUMNS_NAME = (env) => {\n if (env.model.getters.getSelectedZones().length > 1) {\n return _lt(\"Clear columns\");\n }\n let first;\n let last;\n const activeCols = env.model.getters.getActiveCols();\n if (activeCols.size !== 0) {\n first = Math.min(...activeCols);\n last = Math.max(...activeCols);\n }\n else {\n const zone = env.model.getters.getSelectedZones()[0];\n first = zone.left;\n last = zone.right;\n }\n if (first === last) {\n return _lt(\"Clear column %s\", numberToLetters(first));\n }\n return _lt(\"Clear columns %s - %s\", numberToLetters(first), numberToLetters(last));\n };\n const DELETE_CONTENT_COLUMNS_ACTION = (env) => {\n const sheetId = env.model.getters.getActiveSheetId();\n const target = [...env.model.getters.getActiveCols()].map((index) => env.model.getters.getColsZone(sheetId, index, index));\n env.model.dispatch(\"DELETE_CONTENT\", {\n target,\n sheetId: env.model.getters.getActiveSheetId(),\n });\n };\n const REMOVE_ROWS_NAME = (env) => {\n if (env.model.getters.getSelectedZones().length > 1) {\n return _lt(\"Delete rows\");\n }\n let first;\n let last;\n const activesRows = env.model.getters.getActiveRows();\n if (activesRows.size !== 0) {\n first = Math.min(...activesRows);\n last = Math.max(...activesRows);\n }\n else {\n const zone = env.model.getters.getSelectedZones()[0];\n first = zone.top;\n last = zone.bottom;\n }\n if (first === last) {\n return _lt(\"Delete row %s\", (first + 1).toString());\n }\n return _lt(\"Delete rows %s - %s\", (first + 1).toString(), (last + 1).toString());\n };\n const REMOVE_ROWS_ACTION = (env) => {\n let rows = [...env.model.getters.getActiveRows()];\n if (!rows.length) {\n const zone = env.model.getters.getSelectedZones()[0];\n for (let i = zone.top; i <= zone.bottom; i++) {\n rows.push(i);\n }\n }\n env.model.dispatch(\"REMOVE_COLUMNS_ROWS\", {\n sheetId: env.model.getters.getActiveSheetId(),\n dimension: \"ROW\",\n elements: rows,\n });\n };\n const REMOVE_COLUMNS_NAME = (env) => {\n if (env.model.getters.getSelectedZones().length > 1) {\n return _lt(\"Delete columns\");\n }\n let first;\n let last;\n const activeCols = env.model.getters.getActiveCols();\n if (activeCols.size !== 0) {\n first = Math.min(...activeCols);\n last = Math.max(...activeCols);\n }\n else {\n const zone = env.model.getters.getSelectedZones()[0];\n first = zone.left;\n last = zone.right;\n }\n if (first === last) {\n return _lt(\"Delete column %s\", numberToLetters(first));\n }\n return _lt(\"Delete columns %s - %s\", numberToLetters(first), numberToLetters(last));\n };\n const REMOVE_COLUMNS_ACTION = (env) => {\n let columns = [...env.model.getters.getActiveCols()];\n if (!columns.length) {\n const zone = env.model.getters.getSelectedZones()[0];\n for (let i = zone.left; i <= zone.right; i++) {\n columns.push(i);\n }\n }\n env.model.dispatch(\"REMOVE_COLUMNS_ROWS\", {\n sheetId: env.model.getters.getActiveSheetId(),\n dimension: \"COL\",\n elements: columns,\n });\n };\n const INSERT_CELL_SHIFT_DOWN = (env) => {\n const zone = env.model.getters.getSelectedZone();\n const result = env.model.dispatch(\"INSERT_CELL\", { zone, shiftDimension: \"ROW\" });\n handlePasteResult(env, result);\n };\n const INSERT_CELL_SHIFT_RIGHT = (env) => {\n const zone = env.model.getters.getSelectedZone();\n const result = env.model.dispatch(\"INSERT_CELL\", { zone, shiftDimension: \"COL\" });\n handlePasteResult(env, result);\n };\n const DELETE_CELL_SHIFT_UP = (env) => {\n const zone = env.model.getters.getSelectedZone();\n const result = env.model.dispatch(\"DELETE_CELL\", { zone, shiftDimension: \"ROW\" });\n handlePasteResult(env, result);\n };\n const DELETE_CELL_SHIFT_LEFT = (env) => {\n const zone = env.model.getters.getSelectedZone();\n const result = env.model.dispatch(\"DELETE_CELL\", { zone, shiftDimension: \"COL\" });\n handlePasteResult(env, result);\n };\n const MENU_INSERT_ROWS_BEFORE_NAME = (env) => {\n const number = getRowsNumber(env);\n if (number === 1) {\n return _lt(\"Row above\");\n }\n return _lt(\"%s Rows above\", number.toString());\n };\n const ROW_INSERT_ROWS_BEFORE_NAME = (env) => {\n const number = getRowsNumber(env);\n return number === 1 ? _lt(\"Insert row above\") : _lt(\"Insert %s rows above\", number.toString());\n };\n const CELL_INSERT_ROWS_BEFORE_NAME = (env) => {\n const number = getRowsNumber(env);\n if (number === 1) {\n return _lt(\"Insert row\");\n }\n return _lt(\"Insert %s rows\", number.toString());\n };\n const INSERT_ROWS_BEFORE_ACTION = (env) => {\n const activeRows = env.model.getters.getActiveRows();\n let row;\n let quantity;\n if (activeRows.size) {\n row = Math.min(...activeRows);\n quantity = activeRows.size;\n }\n else {\n const zone = env.model.getters.getSelectedZones()[0];\n row = zone.top;\n quantity = zone.bottom - zone.top + 1;\n }\n env.model.dispatch(\"ADD_COLUMNS_ROWS\", {\n sheetId: env.model.getters.getActiveSheetId(),\n position: \"before\",\n base: row,\n quantity,\n dimension: \"ROW\",\n });\n };\n const MENU_INSERT_ROWS_AFTER_NAME = (env) => {\n const number = getRowsNumber(env);\n if (number === 1) {\n return _lt(\"Row below\");\n }\n return _lt(\"%s Rows below\", number.toString());\n };\n const ROW_INSERT_ROWS_AFTER_NAME = (env) => {\n const number = getRowsNumber(env);\n return number === 1 ? _lt(\"Insert row below\") : _lt(\"Insert %s rows below\", number.toString());\n };\n const INSERT_ROWS_AFTER_ACTION = (env) => {\n const activeRows = env.model.getters.getActiveRows();\n let row;\n let quantity;\n if (activeRows.size) {\n row = Math.max(...activeRows);\n quantity = activeRows.size;\n }\n else {\n const zone = env.model.getters.getSelectedZones()[0];\n row = zone.bottom;\n quantity = zone.bottom - zone.top + 1;\n }\n env.model.dispatch(\"ADD_COLUMNS_ROWS\", {\n sheetId: env.model.getters.getActiveSheetId(),\n position: \"after\",\n base: row,\n quantity,\n dimension: \"ROW\",\n });\n };\n const MENU_INSERT_COLUMNS_BEFORE_NAME = (env) => {\n const number = getColumnsNumber(env);\n if (number === 1) {\n return _lt(\"Column left\");\n }\n return _lt(\"%s Columns left\", number.toString());\n };\n const COLUMN_INSERT_COLUMNS_BEFORE_NAME = (env) => {\n const number = getColumnsNumber(env);\n return number === 1\n ? _lt(\"Insert column left\")\n : _lt(\"Insert %s columns left\", number.toString());\n };\n const CELL_INSERT_COLUMNS_BEFORE_NAME = (env) => {\n const number = getColumnsNumber(env);\n if (number === 1) {\n return _lt(\"Insert column\");\n }\n return _lt(\"Insert %s columns\", number.toString());\n };\n const INSERT_COLUMNS_BEFORE_ACTION = (env) => {\n const activeCols = env.model.getters.getActiveCols();\n let column;\n let quantity;\n if (activeCols.size) {\n column = Math.min(...activeCols);\n quantity = activeCols.size;\n }\n else {\n const zone = env.model.getters.getSelectedZones()[0];\n column = zone.left;\n quantity = zone.right - zone.left + 1;\n }\n env.model.dispatch(\"ADD_COLUMNS_ROWS\", {\n sheetId: env.model.getters.getActiveSheetId(),\n position: \"before\",\n dimension: \"COL\",\n base: column,\n quantity,\n });\n };\n const MENU_INSERT_COLUMNS_AFTER_NAME = (env) => {\n const number = getColumnsNumber(env);\n if (number === 1) {\n return _lt(\"Column right\");\n }\n return _lt(\"%s Columns right\", number.toString());\n };\n const COLUMN_INSERT_COLUMNS_AFTER_NAME = (env) => {\n const number = getColumnsNumber(env);\n return number === 1\n ? _lt(\"Insert column right\")\n : _lt(\"Insert %s columns right\", number.toString());\n };\n const INSERT_COLUMNS_AFTER_ACTION = (env) => {\n const activeCols = env.model.getters.getActiveCols();\n let column;\n let quantity;\n if (activeCols.size) {\n column = Math.max(...activeCols);\n quantity = activeCols.size;\n }\n else {\n const zone = env.model.getters.getSelectedZones()[0];\n column = zone.right;\n quantity = zone.right - zone.left + 1;\n }\n env.model.dispatch(\"ADD_COLUMNS_ROWS\", {\n sheetId: env.model.getters.getActiveSheetId(),\n position: \"after\",\n dimension: \"COL\",\n base: column,\n quantity,\n });\n };\n const HIDE_COLUMNS_NAME = (env) => {\n const cols = env.model.getters.getElementsFromSelection(\"COL\");\n let first = cols[0];\n let last = cols[cols.length - 1];\n if (cols.length === 1) {\n return _lt(\"Hide column %s\", numberToLetters(first).toString());\n }\n else if (last - first + 1 === cols.length) {\n return _lt(\"Hide columns %s - %s\", numberToLetters(first).toString(), numberToLetters(last).toString());\n }\n else {\n return _lt(\"Hide columns\");\n }\n };\n const HIDE_COLUMNS_ACTION = (env) => {\n const columns = env.model.getters.getElementsFromSelection(\"COL\");\n env.model.dispatch(\"HIDE_COLUMNS_ROWS\", {\n sheetId: env.model.getters.getActiveSheetId(),\n dimension: \"COL\",\n elements: columns,\n });\n };\n const UNHIDE_ALL_COLUMNS_ACTION = (env) => {\n const sheetId = env.model.getters.getActiveSheetId();\n env.model.dispatch(\"UNHIDE_COLUMNS_ROWS\", {\n sheetId,\n dimension: \"COL\",\n elements: Array.from(Array(env.model.getters.getNumberCols(sheetId)).keys()),\n });\n };\n const UNHIDE_COLUMNS_ACTION = (env) => {\n const columns = env.model.getters.getElementsFromSelection(\"COL\");\n env.model.dispatch(\"UNHIDE_COLUMNS_ROWS\", {\n sheetId: env.model.getters.getActiveSheetId(),\n dimension: \"COL\",\n elements: columns,\n });\n };\n const HIDE_ROWS_NAME = (env) => {\n const rows = env.model.getters.getElementsFromSelection(\"ROW\");\n let first = rows[0];\n let last = rows[rows.length - 1];\n if (rows.length === 1) {\n return _lt(\"Hide row %s\", (first + 1).toString());\n }\n else if (last - first + 1 === rows.length) {\n return _lt(\"Hide rows %s - %s\", (first + 1).toString(), (last + 1).toString());\n }\n else {\n return _lt(\"Hide rows\");\n }\n };\n const HIDE_ROWS_ACTION = (env) => {\n const rows = env.model.getters.getElementsFromSelection(\"ROW\");\n env.model.dispatch(\"HIDE_COLUMNS_ROWS\", {\n sheetId: env.model.getters.getActiveSheetId(),\n dimension: \"ROW\",\n elements: rows,\n });\n };\n const UNHIDE_ALL_ROWS_ACTION = (env) => {\n const sheetId = env.model.getters.getActiveSheetId();\n env.model.dispatch(\"UNHIDE_COLUMNS_ROWS\", {\n sheetId,\n dimension: \"ROW\",\n elements: Array.from(Array(env.model.getters.getNumberRows(sheetId)).keys()),\n });\n };\n const UNHIDE_ROWS_ACTION = (env) => {\n const columns = env.model.getters.getElementsFromSelection(\"ROW\");\n env.model.dispatch(\"UNHIDE_COLUMNS_ROWS\", {\n sheetId: env.model.getters.getActiveSheetId(),\n dimension: \"ROW\",\n elements: columns,\n });\n };\n //------------------------------------------------------------------------------\n // Sheets\n //------------------------------------------------------------------------------\n const CREATE_SHEET_ACTION = (env) => {\n const activeSheetId = env.model.getters.getActiveSheetId();\n const position = env.model.getters.getSheetIds().indexOf(activeSheetId) + 1;\n const sheetId = env.model.uuidGenerator.uuidv4();\n env.model.dispatch(\"CREATE_SHEET\", { sheetId, position });\n env.model.dispatch(\"ACTIVATE_SHEET\", { sheetIdFrom: activeSheetId, sheetIdTo: sheetId });\n };\n //------------------------------------------------------------------------------\n // Charts\n //------------------------------------------------------------------------------\n const CREATE_CHART = (env) => {\n const getters = env.model.getters;\n const id = env.model.uuidGenerator.uuidv4();\n const sheetId = getters.getActiveSheetId();\n if (getZoneArea(env.model.getters.getSelectedZone()) === 1) {\n env.model.selection.selectTableAroundSelection();\n }\n const size = { width: DEFAULT_FIGURE_WIDTH, height: DEFAULT_FIGURE_HEIGHT };\n const position = getChartPositionAtCenterOfViewport(getters, size);\n const result = env.model.dispatch(\"CREATE_CHART\", {\n sheetId,\n id,\n position,\n size,\n definition: getSmartChartDefinition(env.model.getters.getSelectedZone(), env.model.getters),\n });\n if (result.isSuccessful) {\n env.model.dispatch(\"SELECT_FIGURE\", { id });\n env.openSidePanel(\"ChartPanel\");\n }\n };\n //------------------------------------------------------------------------------\n // Image\n //------------------------------------------------------------------------------\n async function requestImage(env) {\n try {\n return await env.imageProvider.requestImage();\n }\n catch {\n env.raiseError(_lt(\"An unexpected error occurred during the image transfer\"));\n return undefined;\n }\n }\n const CREATE_IMAGE = async (env) => {\n if (env.imageProvider) {\n const sheetId = env.model.getters.getActiveSheetId();\n const figureId = env.model.uuidGenerator.uuidv4();\n const image = await requestImage(env);\n if (!image) {\n throw new Error(\"No image provider was given to the environment\");\n }\n const size = getMaxFigureSize(env.model.getters, image.size);\n const position = centerFigurePosition(env.model.getters, size);\n env.model.dispatch(\"CREATE_IMAGE\", {\n sheetId,\n figureId,\n position,\n size,\n definition: image,\n });\n }\n };\n //------------------------------------------------------------------------------\n // Style/Format\n //------------------------------------------------------------------------------\n const FORMAT_AUTOMATIC_ACTION = (env) => setFormatter(env, \"\");\n const FORMAT_NUMBER_ACTION = (env) => setFormatter(env, \"#,##0.00\");\n const FORMAT_PERCENT_ACTION = (env) => setFormatter(env, \"0.00%\");\n const FORMAT_CURRENCY_ACTION = (env) => setFormatter(env, \"[$$]#,##0.00\");\n const FORMAT_CURRENCY_ROUNDED_ACTION = (env) => setFormatter(env, \"[$$]#,##0\");\n const FORMAT_DATE_ACTION = (env) => setFormatter(env, \"m/d/yyyy\");\n const FORMAT_TIME_ACTION = (env) => setFormatter(env, \"hh:mm:ss a\");\n const FORMAT_DATE_TIME_ACTION = (env) => setFormatter(env, \"m/d/yyyy hh:mm:ss\");\n const FORMAT_DURATION_ACTION = (env) => setFormatter(env, \"hhhh:mm:ss\");\n const FORMAT_BOLD_ACTION = (env) => setStyle(env, { bold: !env.model.getters.getCurrentStyle().bold });\n const FORMAT_ITALIC_ACTION = (env) => setStyle(env, { italic: !env.model.getters.getCurrentStyle().italic });\n const FORMAT_STRIKETHROUGH_ACTION = (env) => setStyle(env, { strikethrough: !env.model.getters.getCurrentStyle().strikethrough });\n const FORMAT_UNDERLINE_ACTION = (env) => setStyle(env, { underline: !env.model.getters.getCurrentStyle().underline });\n const FORMAT_CLEARFORMAT_ACTION = (env) => {\n env.model.dispatch(\"CLEAR_FORMATTING\", {\n sheetId: env.model.getters.getActiveSheetId(),\n target: env.model.getters.getSelectedZones(),\n });\n };\n //------------------------------------------------------------------------------\n // Side panel\n //------------------------------------------------------------------------------\n const OPEN_CF_SIDEPANEL_ACTION = (env) => {\n env.openSidePanel(\"ConditionalFormatting\", { selection: env.model.getters.getSelectedZones() });\n };\n const OPEN_FAR_SIDEPANEL_ACTION = (env) => {\n env.openSidePanel(\"FindAndReplace\", {});\n };\n const OPEN_CUSTOM_CURRENCY_SIDEPANEL_ACTION = (env) => {\n env.openSidePanel(\"CustomCurrency\", {});\n };\n const INSERT_LINK = (env) => {\n let { col, row } = env.model.getters.getActivePosition();\n env.model.dispatch(\"OPEN_CELL_POPOVER\", { col, row, popoverType: \"LinkEditor\" });\n };\n //------------------------------------------------------------------------------\n // Filters action\n //------------------------------------------------------------------------------\n const FILTERS_CREATE_FILTER_TABLE = (env) => {\n const sheetId = env.model.getters.getActiveSheetId();\n const selection = env.model.getters.getSelection().zones;\n interactiveAddFilter(env, sheetId, selection);\n };\n const FILTERS_REMOVE_FILTER_TABLE = (env) => {\n const sheetId = env.model.getters.getActiveSheetId();\n env.model.dispatch(\"REMOVE_FILTER_TABLE\", {\n sheetId,\n target: env.model.getters.getSelectedZones(),\n });\n };\n const SELECTION_CONTAINS_FILTER = (env) => {\n const sheetId = env.model.getters.getActiveSheetId();\n const selectedZones = env.model.getters.getSelectedZones();\n return env.model.getters.doesZonesContainFilter(sheetId, selectedZones);\n };\n const SELECTION_IS_CONTINUOUS = (env) => {\n const selectedZones = env.model.getters.getSelectedZones();\n return areZonesContinuous(...selectedZones);\n };\n //------------------------------------------------------------------------------\n // Sorting action\n //------------------------------------------------------------------------------\n const SORT_CELLS_ASCENDING = (env) => {\n const { anchor, zones } = env.model.getters.getSelection();\n const sheetId = env.model.getters.getActiveSheetId();\n interactiveSortSelection(env, sheetId, anchor.cell, zones[0], \"ascending\");\n };\n const SORT_CELLS_DESCENDING = (env) => {\n const { anchor, zones } = env.model.getters.getSelection();\n const sheetId = env.model.getters.getActiveSheetId();\n interactiveSortSelection(env, sheetId, anchor.cell, zones[0], \"descending\");\n };\n const IS_ONLY_ONE_RANGE = (env) => {\n return env.model.getters.getSelectedZones().length === 1;\n };\n\n //------------------------------------------------------------------------------\n // Context Menu Registry\n //------------------------------------------------------------------------------\n const cellMenuRegistry = new MenuItemRegistry();\n cellMenuRegistry\n .add(\"cut\", {\n name: _lt(\"Cut\"),\n description: \"Ctrl+X\",\n sequence: 10,\n action: CUT_ACTION,\n })\n .add(\"copy\", {\n name: _lt(\"Copy\"),\n description: \"Ctrl+C\",\n sequence: 20,\n isReadonlyAllowed: true,\n action: COPY_ACTION,\n })\n .add(\"paste\", {\n name: _lt(\"Paste\"),\n description: \"Ctrl+V\",\n sequence: 30,\n action: PASTE_ACTION,\n })\n .add(\"paste_special\", {\n name: _lt(\"Paste special\"),\n sequence: 40,\n separator: true,\n isVisible: IS_NOT_CUT_OPERATION,\n })\n .addChild(\"paste_value_only\", [\"paste_special\"], {\n name: _lt(\"Paste values only\"),\n sequence: 10,\n action: PASTE_VALUE_ACTION,\n })\n .addChild(\"paste_format_only\", [\"paste_special\"], {\n name: _lt(\"Paste format only\"),\n sequence: 20,\n action: PASTE_FORMAT_ACTION,\n })\n .add(\"add_row_before\", {\n name: CELL_INSERT_ROWS_BEFORE_NAME,\n sequence: 70,\n action: INSERT_ROWS_BEFORE_ACTION,\n isVisible: IS_ONLY_ONE_RANGE,\n })\n .add(\"add_column_before\", {\n name: CELL_INSERT_COLUMNS_BEFORE_NAME,\n sequence: 90,\n action: INSERT_COLUMNS_BEFORE_ACTION,\n isVisible: IS_ONLY_ONE_RANGE,\n })\n .add(\"insert_cell\", {\n name: _lt(\"Insert cells\"),\n sequence: 100,\n isVisible: IS_ONLY_ONE_RANGE,\n separator: true,\n })\n .addChild(\"insert_cell_down\", [\"insert_cell\"], {\n name: _lt(\"Shift down\"),\n sequence: 10,\n action: INSERT_CELL_SHIFT_DOWN,\n })\n .addChild(\"insert_cell_right\", [\"insert_cell\"], {\n name: _lt(\"Shift right\"),\n sequence: 20,\n action: INSERT_CELL_SHIFT_RIGHT,\n })\n .add(\"delete_row\", {\n name: REMOVE_ROWS_NAME,\n sequence: 110,\n action: REMOVE_ROWS_ACTION,\n isVisible: IS_ONLY_ONE_RANGE,\n })\n .add(\"delete_column\", {\n name: REMOVE_COLUMNS_NAME,\n sequence: 120,\n action: REMOVE_COLUMNS_ACTION,\n isVisible: IS_ONLY_ONE_RANGE,\n })\n .add(\"delete_cell\", {\n name: _lt(\"Delete cells\"),\n sequence: 130,\n isVisible: IS_ONLY_ONE_RANGE,\n })\n .addChild(\"delete_cell_up\", [\"delete_cell\"], {\n name: _lt(\"Shift up\"),\n sequence: 10,\n action: DELETE_CELL_SHIFT_UP,\n })\n .addChild(\"delete_cell_down\", [\"delete_cell\"], {\n name: _lt(\"Shift left\"),\n sequence: 20,\n action: DELETE_CELL_SHIFT_LEFT,\n })\n .add(\"insert_link\", {\n name: _lt(\"Insert link\"),\n separator: true,\n sequence: 150,\n action: INSERT_LINK,\n });\n\n const colMenuRegistry = new MenuItemRegistry();\n colMenuRegistry\n .add(\"cut\", {\n name: _lt(\"Cut\"),\n description: \"Ctrl+X\",\n sequence: 10,\n action: CUT_ACTION,\n })\n .add(\"copy\", {\n name: _lt(\"Copy\"),\n description: \"Ctrl+C\",\n sequence: 20,\n isReadonlyAllowed: true,\n action: COPY_ACTION,\n })\n .add(\"paste\", {\n name: _lt(\"Paste\"),\n description: \"Ctrl+V\",\n sequence: 30,\n action: PASTE_ACTION,\n })\n .add(\"paste_special\", {\n name: _lt(\"Paste special\"),\n sequence: 40,\n separator: true,\n isVisible: IS_NOT_CUT_OPERATION,\n })\n .addChild(\"paste_value_only\", [\"paste_special\"], {\n name: _lt(\"Paste value only\"),\n sequence: 10,\n action: PASTE_VALUE_ACTION,\n })\n .addChild(\"paste_format_only\", [\"paste_special\"], {\n name: _lt(\"Paste format only\"),\n sequence: 20,\n action: PASTE_FORMAT_ACTION,\n })\n .add(\"sort_columns\", {\n name: (env) => env.model.getters.getActiveCols().size > 1 ? _lt(\"Sort columns\") : _lt(\"Sort column\"),\n sequence: 50,\n isVisible: IS_ONLY_ONE_RANGE,\n separator: true,\n })\n .addChild(\"sort_ascending\", [\"sort_columns\"], {\n name: _lt(\"Ascending (A \u27f6 Z)\"),\n sequence: 10,\n action: SORT_CELLS_ASCENDING,\n })\n .addChild(\"sort_descending\", [\"sort_columns\"], {\n name: _lt(\"Descending (Z \u27f6 A)\"),\n sequence: 20,\n action: SORT_CELLS_DESCENDING,\n })\n .add(\"add_column_before\", {\n name: COLUMN_INSERT_COLUMNS_BEFORE_NAME,\n sequence: 70,\n action: INSERT_COLUMNS_BEFORE_ACTION,\n })\n .add(\"add_column_after\", {\n name: COLUMN_INSERT_COLUMNS_AFTER_NAME,\n sequence: 80,\n action: INSERT_COLUMNS_AFTER_ACTION,\n })\n .add(\"delete_column\", {\n name: REMOVE_COLUMNS_NAME,\n sequence: 90,\n action: REMOVE_COLUMNS_ACTION,\n })\n .add(\"clear_column\", {\n name: DELETE_CONTENT_COLUMNS_NAME,\n sequence: 100,\n action: DELETE_CONTENT_COLUMNS_ACTION,\n })\n .add(\"hide_columns\", {\n name: HIDE_COLUMNS_NAME,\n sequence: 85,\n action: HIDE_COLUMNS_ACTION,\n isVisible: (env) => {\n const sheetId = env.model.getters.getActiveSheetId();\n const hiddenCols = env.model.getters.getHiddenColsGroups(sheetId).flat();\n return (env.model.getters.getNumberCols(sheetId) >\n hiddenCols.length + env.model.getters.getElementsFromSelection(\"COL\").length);\n },\n separator: true,\n })\n .add(\"unhide_columns\", {\n name: _lt(\"Unhide columns\"),\n sequence: 86,\n action: UNHIDE_COLUMNS_ACTION,\n isVisible: (env) => {\n const hiddenCols = env.model.getters\n .getHiddenColsGroups(env.model.getters.getActiveSheetId())\n .flat();\n const currentCols = env.model.getters.getElementsFromSelection(\"COL\");\n return currentCols.some((col) => hiddenCols.includes(col));\n },\n separator: true,\n })\n .add(\"conditional_formatting\", {\n name: _lt(\"Conditional formatting\"),\n sequence: 110,\n action: OPEN_CF_SIDEPANEL_ACTION,\n });\n\n const rowMenuRegistry = new MenuItemRegistry();\n rowMenuRegistry\n .add(\"cut\", {\n name: _lt(\"Cut\"),\n sequence: 10,\n description: \"Ctrl+X\",\n action: CUT_ACTION,\n })\n .add(\"copy\", {\n name: _lt(\"Copy\"),\n description: \"Ctrl+C\",\n sequence: 20,\n isReadonlyAllowed: true,\n action: COPY_ACTION,\n })\n .add(\"paste\", {\n name: _lt(\"Paste\"),\n description: \"Ctrl+V\",\n sequence: 30,\n action: PASTE_ACTION,\n })\n .add(\"paste_special\", {\n name: _lt(\"Paste special\"),\n sequence: 40,\n separator: true,\n isVisible: IS_NOT_CUT_OPERATION,\n })\n .addChild(\"paste_value_only\", [\"paste_special\"], {\n name: _lt(\"Paste value only\"),\n sequence: 10,\n action: PASTE_VALUE_ACTION,\n })\n .addChild(\"paste_format_only\", [\"paste_special\"], {\n name: _lt(\"Paste format only\"),\n sequence: 20,\n action: PASTE_FORMAT_ACTION,\n })\n .add(\"add_row_before\", {\n name: ROW_INSERT_ROWS_BEFORE_NAME,\n sequence: 50,\n action: INSERT_ROWS_BEFORE_ACTION,\n })\n .add(\"add_row_after\", {\n name: ROW_INSERT_ROWS_AFTER_NAME,\n sequence: 60,\n action: INSERT_ROWS_AFTER_ACTION,\n })\n .add(\"delete_row\", {\n name: REMOVE_ROWS_NAME,\n sequence: 70,\n action: REMOVE_ROWS_ACTION,\n })\n .add(\"clear_row\", {\n name: DELETE_CONTENT_ROWS_NAME,\n sequence: 80,\n action: DELETE_CONTENT_ROWS_ACTION,\n })\n .add(\"hide_rows\", {\n name: HIDE_ROWS_NAME,\n sequence: 85,\n action: HIDE_ROWS_ACTION,\n isVisible: (env) => {\n const sheetId = env.model.getters.getActiveSheetId();\n const hiddenRows = env.model.getters.getHiddenRowsGroups(sheetId).flat();\n return (env.model.getters.getNumberRows(sheetId) >\n hiddenRows.length + env.model.getters.getElementsFromSelection(\"ROW\").length);\n },\n separator: true,\n })\n .add(\"unhide_rows\", {\n name: _lt(\"Unhide rows\"),\n sequence: 86,\n action: UNHIDE_ROWS_ACTION,\n isVisible: (env) => {\n const hiddenRows = env.model.getters\n .getHiddenRowsGroups(env.model.getters.getActiveSheetId())\n .flat();\n const currentRows = env.model.getters.getElementsFromSelection(\"ROW\");\n return currentRows.some((col) => hiddenRows.includes(col));\n },\n separator: true,\n })\n .add(\"conditional_formatting\", {\n name: _lt(\"Conditional formatting\"),\n sequence: 90,\n action: OPEN_CF_SIDEPANEL_ACTION,\n });\n\n function startDnd(onMouseMove, onMouseUp, onMouseDown = () => { }) {\n const _onMouseDown = (ev) => {\n ev.preventDefault();\n onMouseDown(ev);\n };\n const _onMouseMove = (ev) => {\n ev.preventDefault();\n onMouseMove(ev);\n };\n const _onMouseUp = (ev) => {\n ev.preventDefault();\n onMouseUp(ev);\n window.removeEventListener(\"mousedown\", _onMouseDown);\n window.removeEventListener(\"mouseup\", _onMouseUp);\n window.removeEventListener(\"dragstart\", _onDragStart);\n window.removeEventListener(\"mousemove\", _onMouseMove);\n window.removeEventListener(\"wheel\", _onMouseMove);\n };\n function _onDragStart(ev) {\n ev.preventDefault();\n }\n window.addEventListener(\"mousedown\", _onMouseDown);\n window.addEventListener(\"mouseup\", _onMouseUp);\n window.addEventListener(\"dragstart\", _onDragStart);\n window.addEventListener(\"mousemove\", _onMouseMove);\n // mouse wheel on window is by default a passive event.\n // preventDefault() is not allowed in passive event handler.\n // https://chromestatus.com/feature/6662647093133312\n window.addEventListener(\"wheel\", _onMouseMove, { passive: false });\n }\n /**\n * Function to be used during a mousedown event, this function allows to\n * perform actions related to the mousemove and mouseup events and adjusts the viewport\n * when the new position related to the mousemove event is outside of it.\n * Among inputs are two callback functions. First intended for actions performed during\n * the mousemove event, it receives as parameters the current position of the mousemove\n * (occurrence of the current column and the current row). Second intended for actions\n * performed during the mouseup event.\n */\n function dragAndDropBeyondTheViewport(env, cbMouseMove, cbMouseUp, only = false) {\n let timeOutId = null;\n let currentEv;\n let previousEv;\n let startingEv;\n let startingX;\n let startingY;\n const getters = env.model.getters;\n const sheetId = getters.getActiveSheetId();\n const position = gridOverlayPosition();\n let colIndex;\n let rowIndex;\n const onMouseDown = (ev) => {\n previousEv = ev;\n startingEv = ev;\n startingX = startingEv.clientX - position.left;\n startingY = startingEv.clientY - position.top;\n };\n const onMouseMove = (ev) => {\n currentEv = ev;\n if (timeOutId) {\n return;\n }\n const { x: offsetCorrectionX, y: offsetCorrectionY } = getters.getMainViewportCoordinates();\n let { top, left, bottom, right } = getters.getActiveMainViewport();\n let { scrollX, scrollY } = getters.getActiveSheetDOMScrollInfo();\n const { xSplit, ySplit } = getters.getPaneDivisions(sheetId);\n let canEdgeScroll = false;\n let timeoutDelay = MAX_DELAY;\n const x = currentEv.clientX - position.left;\n colIndex = getters.getColIndex(x);\n if (only !== \"vertical\") {\n const previousX = previousEv.clientX - position.left;\n const edgeScrollInfoX = getters.getEdgeScrollCol(x, previousX, startingX);\n if (edgeScrollInfoX.canEdgeScroll) {\n canEdgeScroll = true;\n timeoutDelay = Math.min(timeoutDelay, edgeScrollInfoX.delay);\n let newTarget;\n switch (edgeScrollInfoX.direction) {\n case \"reset\":\n colIndex = xSplit;\n newTarget = xSplit;\n break;\n case 1:\n colIndex = right;\n newTarget = left + 1;\n break;\n case -1:\n colIndex = left - 1;\n while (env.model.getters.isColHidden(sheetId, colIndex)) {\n colIndex--;\n }\n newTarget = colIndex;\n break;\n }\n scrollX = getters.getColDimensions(sheetId, newTarget).start - offsetCorrectionX;\n }\n }\n const y = currentEv.clientY - position.top;\n rowIndex = getters.getRowIndex(y);\n if (only !== \"horizontal\") {\n const previousY = previousEv.clientY - position.top;\n const edgeScrollInfoY = getters.getEdgeScrollRow(y, previousY, startingY);\n if (edgeScrollInfoY.canEdgeScroll) {\n canEdgeScroll = true;\n timeoutDelay = Math.min(timeoutDelay, edgeScrollInfoY.delay);\n let newTarget;\n switch (edgeScrollInfoY.direction) {\n case \"reset\":\n rowIndex = ySplit;\n newTarget = ySplit;\n break;\n case 1:\n rowIndex = bottom;\n newTarget = top + edgeScrollInfoY.direction;\n break;\n case -1:\n rowIndex = top - 1;\n while (env.model.getters.isRowHidden(sheetId, rowIndex)) {\n rowIndex--;\n }\n newTarget = rowIndex;\n break;\n }\n scrollY = env.model.getters.getRowDimensions(sheetId, newTarget).start - offsetCorrectionY;\n }\n }\n if (!canEdgeScroll) {\n if (rowIndex === -1) {\n rowIndex = y < 0 ? 0 : getters.getNumberRows(sheetId) - 1;\n }\n if (colIndex === -1 && x < 0) {\n colIndex = x < 0 ? 0 : getters.getNumberCols(sheetId) - 1;\n }\n }\n cbMouseMove(colIndex, rowIndex, currentEv);\n if (canEdgeScroll) {\n env.model.dispatch(\"SET_VIEWPORT_OFFSET\", { offsetX: scrollX, offsetY: scrollY });\n timeOutId = setTimeout(() => {\n timeOutId = null;\n onMouseMove(currentEv);\n }, Math.round(timeoutDelay));\n }\n previousEv = currentEv;\n };\n const onMouseUp = () => {\n clearTimeout(timeOutId);\n cbMouseUp();\n };\n startDnd(onMouseMove, onMouseUp, onMouseDown);\n }\n\n // -----------------------------------------------------------------------------\n // Autofill\n // -----------------------------------------------------------------------------\n css /* scss */ `\n .o-autofill {\n height: 6px;\n width: 6px;\n border: 1px solid white;\n position: absolute;\n background-color: #1a73e8;\n\n .o-autofill-handler {\n position: absolute;\n height: ${AUTOFILL_EDGE_LENGTH}px;\n width: ${AUTOFILL_EDGE_LENGTH}px;\n\n &:hover {\n cursor: crosshair;\n }\n }\n\n .o-autofill-nextvalue {\n position: absolute;\n background-color: #ffffff;\n border: 1px solid black;\n padding: 5px;\n font-size: 12px;\n pointer-events: none;\n white-space: nowrap;\n }\n }\n`;\n class Autofill extends owl.Component {\n constructor() {\n super(...arguments);\n this.state = owl.useState({\n position: { left: 0, top: 0 },\n handler: false,\n });\n }\n get style() {\n const { left, top } = this.props.position;\n return `top:${top}px;left:${left}px`;\n }\n get styleHandler() {\n let position = this.state.handler ? this.state.position : { left: 0, top: 0 };\n return `top:${position.top}px;left:${position.left}px;`;\n }\n get styleNextvalue() {\n let position = this.state.handler ? this.state.position : { left: 0, top: 0 };\n return `top:${position.top + 5}px;left:${position.left + 15}px;`;\n }\n getTooltip() {\n const tooltip = this.env.model.getters.getAutofillTooltip();\n if (tooltip && !tooltip.component) {\n tooltip.component = TooltipComponent;\n }\n return tooltip;\n }\n onMouseDown(ev) {\n this.state.handler = true;\n this.state.position = { left: 0, top: 0 };\n const { scrollY, scrollX } = this.env.model.getters.getActiveSheetScrollInfo();\n const start = {\n left: ev.clientX + scrollX,\n top: ev.clientY + scrollY,\n };\n let lastCol;\n let lastRow;\n const onMouseUp = () => {\n this.state.handler = false;\n this.env.model.dispatch(\"AUTOFILL\");\n };\n const onMouseMove = (ev) => {\n const position = gridOverlayPosition();\n const { scrollY, scrollX } = this.env.model.getters.getActiveSheetScrollInfo();\n this.state.position = {\n left: ev.clientX - start.left + scrollX,\n top: ev.clientY - start.top + scrollY,\n };\n const col = this.env.model.getters.getColIndex(ev.clientX - position.left);\n const row = this.env.model.getters.getRowIndex(ev.clientY - position.top);\n if (lastCol !== col || lastRow !== row) {\n const activeSheetId = this.env.model.getters.getActiveSheetId();\n const numberOfCols = this.env.model.getters.getNumberCols(activeSheetId);\n const numberOfRows = this.env.model.getters.getNumberRows(activeSheetId);\n lastCol = col === -1 ? lastCol : clip(col, 0, numberOfCols);\n lastRow = row === -1 ? lastRow : clip(row, 0, numberOfRows);\n if (lastCol !== undefined && lastRow !== undefined) {\n this.env.model.dispatch(\"AUTOFILL_SELECT\", { col: lastCol, row: lastRow });\n }\n }\n };\n startDnd(onMouseMove, onMouseUp);\n }\n onDblClick() {\n this.env.model.dispatch(\"AUTOFILL_AUTO\");\n }\n }\n Autofill.template = \"o-spreadsheet-Autofill\";\n Autofill.props = {\n position: Object,\n };\n class TooltipComponent extends owl.Component {\n }\n TooltipComponent.template = owl.xml /* xml */ `\n
\n `;\n TooltipComponent.props = {\n content: String,\n };\n\n css /* scss */ `\n .o-client-tag {\n position: absolute;\n border-top-left-radius: 4px;\n border-top-right-radius: 4px;\n font-size: ${DEFAULT_FONT_SIZE};\n color: white;\n opacity: 0;\n pointer-events: none;\n }\n`;\n class ClientTag extends owl.Component {\n get tagStyle() {\n const { col, row, color } = this.props;\n const { height } = this.env.model.getters.getSheetViewDimensionWithHeaders();\n const { x, y } = this.env.model.getters.getVisibleRect({\n left: col,\n top: row,\n right: col,\n bottom: row,\n });\n return `bottom: ${height - y + 15}px;left: ${x - 1}px;border: 1px solid ${color};background-color: ${color};${this.props.active ? \"opacity:1 !important\" : \"\"}`;\n }\n }\n ClientTag.template = \"o-spreadsheet-ClientTag\";\n ClientTag.props = {\n active: Boolean,\n name: String,\n color: String,\n col: Number,\n row: Number,\n };\n\n //------------------------------------------------------------------------------\n // Arg description DSL\n //------------------------------------------------------------------------------\n const ARG_REGEXP = /(.*?)\\((.*?)\\)(.*)/;\n const ARG_TYPES = [\n \"ANY\",\n \"BOOLEAN\",\n \"DATE\",\n \"NUMBER\",\n \"STRING\",\n \"RANGE\",\n \"RANGE\",\n \"RANGE\",\n \"RANGE\",\n \"RANGE\",\n \"META\",\n ];\n /**\n * This function is meant to be used as a tag for a template strings.\n *\n * Its job is to convert a textual description of the list of arguments into an\n * actual array of Arg, suitable for consumption.\n */\n function args(strings) {\n let lines = strings.split(\"\\n\");\n const result = [];\n for (let l of lines) {\n l = l.trim();\n if (l) {\n result.push(makeArg(l));\n }\n }\n return result;\n }\n function makeArg(str) {\n let parts = str.match(ARG_REGEXP);\n let name = parts[1].trim();\n let types = [];\n let isOptional = false;\n let isRepeating = false;\n let isLazy = false;\n let defaultValue;\n for (let param of parts[2].split(\",\")) {\n const key = param.trim().toUpperCase();\n let type = ARG_TYPES.find((t) => key === t);\n if (type) {\n types.push(type);\n }\n else if (key === \"RANGE\") {\n types.push(\"RANGE\");\n }\n else if (key === \"OPTIONAL\") {\n isOptional = true;\n }\n else if (key === \"REPEATING\") {\n isRepeating = true;\n }\n else if (key === \"LAZY\") {\n isLazy = true;\n }\n else if (key.startsWith(\"DEFAULT=\")) {\n defaultValue = param.trim().slice(8);\n }\n }\n let description = parts[3].trim();\n const result = {\n name,\n description,\n type: types,\n };\n if (isOptional) {\n result.optional = true;\n }\n if (isRepeating) {\n result.repeating = true;\n }\n if (isLazy) {\n result.lazy = true;\n }\n if (defaultValue !== undefined) {\n result.default = true;\n result.defaultValue = defaultValue;\n }\n return result;\n }\n /**\n * This function adds on description more general information derived from the\n * arguments.\n *\n * This information is useful during compilation.\n */\n function addMetaInfoFromArg(addDescr) {\n let countArg = 0;\n let minArg = 0;\n let repeatingArg = 0;\n for (let arg of addDescr.args) {\n countArg++;\n if (!arg.optional && !arg.repeating && !arg.default) {\n minArg++;\n }\n if (arg.repeating) {\n repeatingArg++;\n }\n }\n const descr = addDescr;\n descr.minArgRequired = minArg;\n descr.maxArgPossible = repeatingArg ? Infinity : countArg;\n descr.nbrArgRepeating = repeatingArg;\n descr.getArgToFocus = argTargeting(countArg, repeatingArg);\n return descr;\n }\n /**\n * Returns a function allowing finding which argument corresponds a position\n * in a function. This is particularly useful for functions with repeatable\n * arguments.\n *\n * Indeed the function makes it possible to etablish corespondance between\n * arguments when the number of arguments supplied is greater than the number of\n * arguments defined by the function.\n *\n * Ex:\n *\n * in the formula \"=SUM(11, 55, 66)\" which is defined like this \"SUM(value1, [value2, ...])\"\n * - 11 corresponds to the value1 argument => position will be 1\n * - 55 corresponds to the [value2, ...] argument => position will be 2\n * - 66 corresponds to the [value2, ...] argument => position will be 2\n *\n * in the formula \"=AVERAGE.WEIGHTED(1, 2, 3, 4, 5, 6)\" which is defined like this\n * \"AVERAGE.WEIGHTED(values, weights, [additional_values, ...], [additional_weights, ...])\"\n * - 1 corresponds to the values argument => position will be 1\n * - 2 corresponds to the weights argument => position will be 2\n * - 3 corresponds to the [additional_values, ...] argument => position will be 3\n * - 4 corresponds to the [additional_weights, ...] argument => position will be 4\n * - 5 corresponds to the [additional_values, ...] argument => position will be 3\n * - 6 corresponds to the [additional_weights, ...] argument => position will be 4\n */\n function argTargeting(countArg, repeatingArg) {\n if (!repeatingArg) {\n return (argPosition) => argPosition;\n }\n if (repeatingArg === 1) {\n return (argPosition) => Math.min(argPosition, countArg);\n }\n const argBeforeRepeat = countArg - repeatingArg;\n return (argPosition) => {\n if (argPosition <= argBeforeRepeat) {\n return argPosition;\n }\n const argAfterRepeat = (argPosition - argBeforeRepeat) % repeatingArg || repeatingArg;\n return argBeforeRepeat + argAfterRepeat;\n };\n }\n //------------------------------------------------------------------------------\n // Argument validation\n //------------------------------------------------------------------------------\n function validateArguments(args) {\n let previousArgRepeating = false;\n let previousArgOptional = false;\n let previousArgDefault = false;\n for (let current of args) {\n if (current.type.includes(\"META\") && current.type.length > 1) {\n throw new Error(_lt(\"Function ${name} has an argument that has been declared with more than one type whose type 'META'. The 'META' type can only be declared alone.\"));\n }\n if (previousArgRepeating && !current.repeating) {\n throw new Error(_lt(\"Function ${name} has no-repeatable arguments declared after repeatable ones. All repeatable arguments must be declared last.\"));\n }\n const previousIsOptional = previousArgOptional || previousArgRepeating || previousArgDefault;\n const currentIsntOptional = !(current.optional || current.repeating || current.default);\n if (previousIsOptional && currentIsntOptional) {\n throw new Error(_lt(\"Function ${name} has at mandatory arguments declared after optional ones. All optional arguments must be after all mandatory arguments.\"));\n }\n previousArgRepeating = current.repeating;\n previousArgOptional = current.optional;\n previousArgDefault = current.default;\n }\n }\n\n // HELPERS\n const SORT_TYPES_ORDER = [\"number\", \"string\", \"boolean\", \"undefined\"];\n function assert(condition, message) {\n if (!condition()) {\n throw new Error(message);\n }\n }\n // -----------------------------------------------------------------------------\n // FORMAT FUNCTIONS\n // -----------------------------------------------------------------------------\n const expectNumberValueError = (value) => _lt(\"The function [[FUNCTION_NAME]] expects a number value, but '%s' is a string, and cannot be coerced to a number.\", value);\n function toNumber(value) {\n switch (typeof value) {\n case \"number\":\n return value;\n case \"boolean\":\n return value ? 1 : 0;\n case \"string\":\n if (isNumber(value) || value === \"\") {\n return parseNumber(value);\n }\n const internalDate = parseDateTime(value);\n if (internalDate) {\n return internalDate.value;\n }\n throw new Error(expectNumberValueError(value));\n default:\n return 0;\n }\n }\n function strictToNumber(value) {\n if (value === \"\") {\n throw new Error(expectNumberValueError(value));\n }\n return toNumber(value);\n }\n function toString(value) {\n switch (typeof value) {\n case \"string\":\n return value;\n case \"number\":\n return value.toString();\n case \"boolean\":\n return value ? \"TRUE\" : \"FALSE\";\n default:\n return \"\";\n }\n }\n /** Normalize string by setting it to lowercase and replacing accent letters with plain letters */\n function normalizeString(str) {\n return str\n .toLowerCase()\n .normalize(\"NFD\")\n .replace(/[\\u0300-\\u036f]/g, \"\");\n }\n /**\n * Normalize a value.\n * If the cell value is a string, this will set it to lowercase and replacing accent letters with plain letters\n */\n function normalizeValue(value) {\n return typeof value === \"string\" ? normalizeString(value) : value;\n }\n const expectBooleanValueError = (value) => _lt(\"The function [[FUNCTION_NAME]] expects a boolean value, but '%s' is a text, and cannot be coerced to a number.\", value);\n function toBoolean(value) {\n switch (typeof value) {\n case \"boolean\":\n return value;\n case \"string\":\n if (value) {\n let uppercaseVal = value.toUpperCase();\n if (uppercaseVal === \"TRUE\") {\n return true;\n }\n if (uppercaseVal === \"FALSE\") {\n return false;\n }\n throw new Error(expectBooleanValueError(value));\n }\n else {\n return false;\n }\n case \"number\":\n return value ? true : false;\n default:\n return false;\n }\n }\n function strictToBoolean(value) {\n if (value === \"\") {\n throw new Error(expectBooleanValueError(value));\n }\n return toBoolean(value);\n }\n function toJsDate(value) {\n return numberToJsDate(toNumber(value));\n }\n // -----------------------------------------------------------------------------\n // VISIT FUNCTIONS\n // -----------------------------------------------------------------------------\n function visitArgs(args, cellCb, dataCb) {\n for (let arg of args) {\n if (Array.isArray(arg)) {\n // arg is ref to a Cell/Range\n const lenRow = arg.length;\n const lenCol = arg[0].length;\n for (let y = 0; y < lenCol; y++) {\n for (let x = 0; x < lenRow; x++) {\n cellCb(arg[x][y]);\n }\n }\n }\n else {\n // arg is set directly in the formula function\n dataCb(arg);\n }\n }\n }\n function visitAny(args, cb) {\n visitArgs(args, cb, cb);\n }\n function visitNumbers(args, cb) {\n visitArgs(args, (cellValue) => {\n if (typeof cellValue === \"number\") {\n cb(cellValue);\n }\n }, (argValue) => {\n cb(strictToNumber(argValue));\n });\n }\n // -----------------------------------------------------------------------------\n // REDUCE FUNCTIONS\n // -----------------------------------------------------------------------------\n function reduceArgs(args, cellCb, dataCb, initialValue) {\n let val = initialValue;\n for (let arg of args) {\n if (Array.isArray(arg)) {\n // arg is ref to a Cell/Range\n const lenRow = arg.length;\n const lenCol = arg[0].length;\n for (let y = 0; y < lenCol; y++) {\n for (let x = 0; x < lenRow; x++) {\n val = cellCb(val, arg[x][y]);\n }\n }\n }\n else {\n // arg is set directly in the formula function\n val = dataCb(val, arg);\n }\n }\n return val;\n }\n function reduceAny(args, cb, initialValue) {\n return reduceArgs(args, cb, cb, initialValue);\n }\n function reduceNumbers(args, cb, initialValue) {\n return reduceArgs(args, (acc, ArgValue) => {\n if (typeof ArgValue === \"number\") {\n return cb(acc, ArgValue);\n }\n return acc;\n }, (acc, argValue) => {\n return cb(acc, strictToNumber(argValue));\n }, initialValue);\n }\n function reduceNumbersTextAs0(args, cb, initialValue) {\n return reduceArgs(args, (acc, ArgValue) => {\n if (ArgValue !== undefined && ArgValue !== null) {\n if (typeof ArgValue === \"number\") {\n return cb(acc, ArgValue);\n }\n else if (typeof ArgValue === \"boolean\") {\n return cb(acc, toNumber(ArgValue));\n }\n else {\n return cb(acc, 0);\n }\n }\n return acc;\n }, (acc, argValue) => {\n return cb(acc, toNumber(argValue));\n }, initialValue);\n }\n // -----------------------------------------------------------------------------\n // CONDITIONAL EXPLORE FUNCTIONS\n // -----------------------------------------------------------------------------\n /**\n * This function allows to visit arguments and stop the visit if necessary.\n * It is mainly used to bypass argument evaluation for functions like OR or AND.\n */\n function conditionalVisitArgs(args, cellCb, dataCb) {\n for (let arg of args) {\n if (Array.isArray(arg)) {\n // arg is ref to a Cell/Range\n const lenRow = arg.length;\n const lenCol = arg[0].length;\n for (let y = 0; y < lenCol; y++) {\n for (let x = 0; x < lenRow; x++) {\n if (!cellCb(arg[x][y]))\n return;\n }\n }\n }\n else {\n // arg is set directly in the formula function\n if (!dataCb(arg))\n return;\n }\n }\n }\n function conditionalVisitBoolean(args, cb) {\n return conditionalVisitArgs(args, (ArgValue) => {\n if (typeof ArgValue === \"boolean\") {\n return cb(ArgValue);\n }\n if (typeof ArgValue === \"number\") {\n return cb(ArgValue ? true : false);\n }\n return true;\n }, (argValue) => {\n if (argValue !== undefined && argValue !== null) {\n return cb(strictToBoolean(argValue));\n }\n return true;\n });\n }\n function getPredicate(descr, isQuery) {\n let operator;\n let operand;\n let subString = descr.substring(0, 2);\n if (subString === \"<=\" || subString === \">=\" || subString === \"<>\") {\n operator = subString;\n operand = descr.substring(2);\n }\n else {\n subString = descr.substring(0, 1);\n if (subString === \"<\" || subString === \">\" || subString === \"=\") {\n operator = subString;\n operand = descr.substring(1);\n }\n else {\n operator = \"=\";\n operand = descr;\n }\n }\n if (isNumber(operand)) {\n operand = toNumber(operand);\n }\n else if (operand === \"TRUE\" || operand === \"FALSE\") {\n operand = toBoolean(operand);\n }\n const result = { operator, operand };\n if (typeof operand === \"string\") {\n if (isQuery) {\n operand += \"*\";\n }\n result.regexp = operandToRegExp(operand);\n }\n return result;\n }\n function operandToRegExp(operand) {\n let exp = \"\";\n let predecessor = \"\";\n for (let char of operand) {\n if (char === \"?\" && predecessor !== \"~\") {\n exp += \".\";\n }\n else if (char === \"*\" && predecessor !== \"~\") {\n exp += \".*\";\n }\n else {\n if (char === \"*\" || char === \"?\") {\n //remove \"~\"\n exp = exp.slice(0, -1);\n }\n if ([\"^\", \".\", \"[\", \"]\", \"$\", \"(\", \")\", \"*\", \"+\", \"?\", \"|\", \"{\", \"}\", \"\\\\\"].includes(char)) {\n exp += \"\\\\\";\n }\n exp += char;\n }\n predecessor = char;\n }\n return new RegExp(\"^\" + exp + \"$\", \"i\");\n }\n function evaluatePredicate(value, criterion) {\n const { operator, operand } = criterion;\n if (value === undefined || operand === undefined) {\n return false;\n }\n if (typeof operand === \"number\" && operator === \"=\") {\n return toString(value) === toString(operand);\n }\n if (operator === \"<>\" || operator === \"=\") {\n let result;\n if (typeof value === typeof operand) {\n if (typeof value === \"string\" && criterion.regexp) {\n result = criterion.regexp.test(value);\n }\n else {\n result = value === operand;\n }\n }\n else {\n result = false;\n }\n return operator === \"=\" ? result : !result;\n }\n if (typeof value === typeof operand) {\n switch (operator) {\n case \"<\":\n return value < operand;\n case \">\":\n return value > operand;\n case \"<=\":\n return value <= operand;\n case \">=\":\n return value >= operand;\n }\n }\n return false;\n }\n /**\n * Functions used especially for predicate evaluation on ranges.\n *\n * Take ranges with same dimensions and take predicates, one for each range.\n * For (i, j) coordinates, if all elements with coordinates (i, j) of each\n * range correspond to the associated predicate, then the function uses a callback\n * function with the parameters \"i\" and \"j\".\n *\n * Syntax:\n * visitMatchingRanges([range1, predicate1, range2, predicate2, ...], cb(i,j), likeSelection)\n *\n * - range1 (range): The range to check against predicate1.\n * - predicate1 (string): The pattern or test to apply to range1.\n * - range2: (range, repeatable) ranges to check.\n * - predicate2 (string, repeatable): Additional pattern or test to apply to range2.\n *\n * - cb(i: number, j: number) => void: the callback function.\n *\n * - isQuery (boolean) indicates if the comparison with a string should be done as a SQL-like query.\n * (Ex1 isQuery = true, predicate = \"abc\", element = \"abcde\": predicate match the element),\n * (Ex2 isQuery = false, predicate = \"abc\", element = \"abcde\": predicate not match the element).\n * (Ex3 isQuery = true, predicate = \"abc\", element = \"abc\": predicate match the element),\n * (Ex4 isQuery = false, predicate = \"abc\", element = \"abc\": predicate match the element).\n */\n function visitMatchingRanges(args, cb, isQuery = false) {\n const countArg = args.length;\n if (countArg % 2 === 1) {\n throw new Error(_lt(`Function [[FUNCTION_NAME]] expects criteria_range and criterion to be in pairs.`));\n }\n const dimRow = args[0].length;\n const dimCol = args[0][0].length;\n let predicates = [];\n for (let i = 0; i < countArg - 1; i += 2) {\n const criteriaRange = args[i];\n if (!Array.isArray(criteriaRange) ||\n criteriaRange.length !== dimRow ||\n criteriaRange[0].length !== dimCol) {\n throw new Error(_lt(`Function [[FUNCTION_NAME]] expects criteria_range to have the same dimension`));\n }\n const description = toString(args[i + 1]);\n predicates.push(getPredicate(description, isQuery));\n }\n for (let i = 0; i < dimRow; i++) {\n for (let j = 0; j < dimCol; j++) {\n let validatedPredicates = true;\n for (let k = 0; k < countArg - 1; k += 2) {\n const criteriaValue = args[k][i][j];\n const criterion = predicates[k / 2];\n validatedPredicates = evaluatePredicate(criteriaValue, criterion);\n if (!validatedPredicates) {\n break;\n }\n }\n if (validatedPredicates) {\n cb(i, j);\n }\n }\n }\n }\n // -----------------------------------------------------------------------------\n // COMMON FUNCTIONS\n // -----------------------------------------------------------------------------\n function getNormalizedValueFromColumnRange(range, index) {\n return normalizeValue(range[0][index]);\n }\n function getNormalizedValueFromRowRange(range, index) {\n return normalizeValue(range[index][0]);\n }\n /**\n * Perform a dichotomic search on an array and return the index of the nearest match.\n *\n * The array should be sorted, if not an incorrect value might be returned. In the case where multiple\n * element of the array match the target, the method will return the first match if the array is sorted\n * in descending order, and the last match if the array is in ascending order.\n *\n *\n * @param data the array in which to search.\n * @param target the value to search.\n * @param mode \"nextGreater/nextSmaller\" : return next greater/smaller value if no exact match is found.\n * @param sortOrder whether the array is sorted in ascending or descending order.\n * @param rangeLength the number of elements to consider in the search array.\n * @param getValueInData function returning the element at index i in the search array.\n */\n function dichotomicSearch(data, target, mode, sortOrder, rangeLength, getValueInData) {\n if (target === null || target === undefined) {\n return -1;\n }\n const targetType = typeof target;\n let matchVal = undefined;\n let matchValIndex = undefined;\n let indexLeft = 0;\n let indexRight = rangeLength - 1;\n let indexMedian;\n let currentIndex;\n let currentVal;\n let currentType;\n while (indexRight - indexLeft >= 0) {\n indexMedian = Math.floor((indexLeft + indexRight) / 2);\n currentIndex = indexMedian;\n currentVal = getValueInData(data, currentIndex);\n currentType = typeof currentVal;\n // 1 - linear search to find value with the same type\n while (indexLeft < currentIndex && targetType !== currentType) {\n currentIndex--;\n currentVal = getValueInData(data, currentIndex);\n currentType = typeof currentVal;\n }\n if (currentType !== targetType || currentVal === undefined) {\n indexLeft = indexMedian + 1;\n continue;\n }\n // 2 - check if value match\n if (mode === \"strict\" && currentVal === target) {\n matchVal = currentVal;\n matchValIndex = currentIndex;\n }\n else if (mode === \"nextSmaller\" && currentVal <= target) {\n if (matchVal === undefined ||\n matchVal < currentVal ||\n (matchVal === currentVal && sortOrder === \"asc\" && matchValIndex < currentIndex) ||\n (matchVal === currentVal && sortOrder === \"desc\" && matchValIndex > currentIndex)) {\n matchVal = currentVal;\n matchValIndex = currentIndex;\n }\n }\n else if (mode === \"nextGreater\" && currentVal >= target) {\n if (matchVal === undefined ||\n matchVal > currentVal ||\n (matchVal === currentVal && sortOrder === \"asc\" && matchValIndex < currentIndex) ||\n (matchVal === currentVal && sortOrder === \"desc\" && matchValIndex > currentIndex)) {\n matchVal = currentVal;\n matchValIndex = currentIndex;\n }\n }\n // 3 - give new indexes for the Binary search\n if ((sortOrder === \"asc\" && currentVal > target) ||\n (sortOrder === \"desc\" && currentVal <= target)) {\n indexRight = currentIndex - 1;\n }\n else {\n indexLeft = indexMedian + 1;\n }\n }\n // note that valMinIndex could be 0\n return matchValIndex !== undefined ? matchValIndex : -1;\n }\n /**\n * Perform a linear search and return the index of the match.\n * -1 is returned if no value is found.\n *\n * Example:\n * - [3, 6, 10], 3 => 0\n * - [3, 6, 10], 6 => 1\n * - [3, 6, 10], 9 => -1\n * - [3, 6, 10], 2 => -1\n *\n * @param data the array to search in.\n * @param target the value to search in the array.\n * @param mode if \"strict\" return exact match index. \"nextGreater\" returns the next greater\n * element from the target and \"nextSmaller\" the next smaller\n * @param numberOfValues the number of elements to consider in the search array.\n * @param getValueInData function returning the element at index i in the search array.\n * @param reverseSearch if true, search in the array starting from the end.\n\n */\n function linearSearch(data, target, mode, numberOfValues, getValueInData, reverseSearch = false) {\n if (target === null || target === undefined)\n return -1;\n const getValue = reverseSearch\n ? (data, i) => getValueInData(data, numberOfValues - i - 1)\n : getValueInData;\n let closestMatch = undefined;\n let closestMatchIndex = -1;\n for (let i = 0; i < numberOfValues; i++) {\n const value = getValue(data, i);\n if (value === target) {\n return reverseSearch ? numberOfValues - i - 1 : i;\n }\n if (mode === \"nextSmaller\") {\n if ((!closestMatch && compareCellValues(target, value) >= 0) ||\n (compareCellValues(target, value) >= 0 && compareCellValues(value, closestMatch) > 0)) {\n closestMatch = value;\n closestMatchIndex = i;\n }\n }\n else if (mode === \"nextGreater\") {\n if ((!closestMatch && compareCellValues(target, value) <= 0) ||\n (compareCellValues(target, value) <= 0 && compareCellValues(value, closestMatch) < 0)) {\n closestMatch = value;\n closestMatchIndex = i;\n }\n }\n }\n return reverseSearch ? numberOfValues - closestMatchIndex - 1 : closestMatchIndex;\n }\n function compareCellValues(left, right) {\n let typeOrder = SORT_TYPES_ORDER.indexOf(typeof left) - SORT_TYPES_ORDER.indexOf(typeof right);\n if (typeOrder === 0) {\n if (typeof left === \"string\" && typeof right === \"string\") {\n typeOrder = left.localeCompare(right);\n }\n else if (typeof left === \"number\" && typeof right === \"number\") {\n typeOrder = left - right;\n }\n else if (typeof left === \"boolean\" && typeof right === \"boolean\") {\n typeOrder = Number(left) - Number(right);\n }\n }\n return typeOrder;\n }\n\n // -----------------------------------------------------------------------------\n // FORMAT.LARGE.NUMBER\n // -----------------------------------------------------------------------------\n const FORMAT_LARGE_NUMBER = {\n description: _lt(`Apply a large number format`),\n args: args(`\n value (number) ${_lt(\"The number.\")}\n unit (string, optional) ${_lt(\"The formatting unit. Use 'k', 'm', or 'b' to force the unit\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (arg, unit) => {\n const value = Math.abs(toNumber(arg.value));\n const format = arg.format;\n if (unit !== undefined) {\n const postFix = unit === null || unit === void 0 ? void 0 : unit.value;\n switch (postFix) {\n case \"k\":\n return createLargeNumberFormat(format, 1e3, \"k\");\n case \"m\":\n return createLargeNumberFormat(format, 1e6, \"m\");\n case \"b\":\n return createLargeNumberFormat(format, 1e9, \"b\");\n default:\n throw new Error(_lt(\"The formatting unit should be 'k', 'm' or 'b'.\"));\n }\n }\n if (value < 1e5) {\n return createLargeNumberFormat(format, 0, \"\");\n }\n else if (value < 1e8) {\n return createLargeNumberFormat(format, 1e3, \"k\");\n }\n else if (value < 1e11) {\n return createLargeNumberFormat(format, 1e6, \"m\");\n }\n return createLargeNumberFormat(format, 1e9, \"b\");\n },\n compute: function (value) {\n return toNumber(value);\n },\n };\n\n var misc$1 = /*#__PURE__*/Object.freeze({\n __proto__: null,\n FORMAT_LARGE_NUMBER: FORMAT_LARGE_NUMBER\n });\n\n const DEFAULT_FACTOR = 1;\n const DEFAULT_MODE = 0;\n const DEFAULT_PLACES = 0;\n const DEFAULT_SIGNIFICANCE = 1;\n // -----------------------------------------------------------------------------\n // ABS\n // -----------------------------------------------------------------------------\n const ABS = {\n description: _lt(\"Absolute value of a number.\"),\n args: args(`\n value (number) ${_lt(\"The number of which to return the absolute value.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (value) {\n return Math.abs(toNumber(value));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ACOS\n // -----------------------------------------------------------------------------\n const ACOS = {\n description: _lt(\"Inverse cosine of a value, in radians.\"),\n args: args(`\n value (number) ${_lt(\"The value for which to calculate the inverse cosine. Must be between -1 and 1, inclusive.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (value) {\n const _value = toNumber(value);\n assert(() => Math.abs(_value) <= 1, _lt(\"The value (%s) must be between -1 and 1 inclusive.\", _value.toString()));\n return Math.acos(_value);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ACOSH\n // -----------------------------------------------------------------------------\n const ACOSH = {\n description: _lt(\"Inverse hyperbolic cosine of a number.\"),\n args: args(`\n value (number) ${_lt(\"The value for which to calculate the inverse hyperbolic cosine. Must be greater than or equal to 1.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (value) {\n const _value = toNumber(value);\n assert(() => _value >= 1, _lt(\"The value (%s) must be greater than or equal to 1.\", _value.toString()));\n return Math.acosh(_value);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ACOT\n // -----------------------------------------------------------------------------\n const ACOT = {\n description: _lt(\"Inverse cotangent of a value.\"),\n args: args(`\n value (number) ${_lt(\"The value for which to calculate the inverse cotangent.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (value) {\n const _value = toNumber(value);\n const sign = Math.sign(_value) || 1;\n // ACOT has two possible configurations:\n // @compatibility Excel: return Math.PI / 2 - Math.atan(toNumber(_value));\n // @compatibility Google: return sign * Math.PI / 2 - Math.atan(toNumber(_value));\n return (sign * Math.PI) / 2 - Math.atan(_value);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ACOTH\n // -----------------------------------------------------------------------------\n const ACOTH = {\n description: _lt(\"Inverse hyperbolic cotangent of a value.\"),\n args: args(`\n value (number) ${_lt(\"The value for which to calculate the inverse hyperbolic cotangent. Must not be between -1 and 1, inclusive.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (value) {\n const _value = toNumber(value);\n assert(() => Math.abs(_value) > 1, _lt(\"The value (%s) cannot be between -1 and 1 inclusive.\", _value.toString()));\n return Math.log((_value + 1) / (_value - 1)) / 2;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ASIN\n // -----------------------------------------------------------------------------\n const ASIN = {\n description: _lt(\"Inverse sine of a value, in radians.\"),\n args: args(`\n value (number) ${_lt(\"The value for which to calculate the inverse sine. Must be between -1 and 1, inclusive.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (value) {\n const _value = toNumber(value);\n assert(() => Math.abs(_value) <= 1, _lt(\"The value (%s) must be between -1 and 1 inclusive.\", _value.toString()));\n return Math.asin(_value);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ASINH\n // -----------------------------------------------------------------------------\n const ASINH = {\n description: _lt(\"Inverse hyperbolic sine of a number.\"),\n args: args(`\n value (number) ${_lt(\"The value for which to calculate the inverse hyperbolic sine.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (value) {\n return Math.asinh(toNumber(value));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ATAN\n // -----------------------------------------------------------------------------\n const ATAN = {\n description: _lt(\"Inverse tangent of a value, in radians.\"),\n args: args(`\n value (number) ${_lt(\"The value for which to calculate the inverse tangent.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (value) {\n return Math.atan(toNumber(value));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ATAN2\n // -----------------------------------------------------------------------------\n const ATAN2 = {\n description: _lt(\"Angle from the X axis to a point (x,y), in radians.\"),\n args: args(`\n x (number) ${_lt(\"The x coordinate of the endpoint of the line segment for which to calculate the angle from the x-axis.\")}\n y (number) ${_lt(\"The y coordinate of the endpoint of the line segment for which to calculate the angle from the x-axis.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (x, y) {\n const _x = toNumber(x);\n const _y = toNumber(y);\n assert(() => _x !== 0 || _y !== 0, _lt(`Function [[FUNCTION_NAME]] caused a divide by zero error.`));\n return Math.atan2(_y, _x);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ATANH\n // -----------------------------------------------------------------------------\n const ATANH = {\n description: _lt(\"Inverse hyperbolic tangent of a number.\"),\n args: args(`\n value (number) ${_lt(\"The value for which to calculate the inverse hyperbolic tangent. Must be between -1 and 1, exclusive.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (value) {\n const _value = toNumber(value);\n assert(() => Math.abs(_value) < 1, _lt(\"The value (%s) must be between -1 and 1 exclusive.\", _value.toString()));\n return Math.atanh(_value);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // CEILING\n // -----------------------------------------------------------------------------\n const CEILING = {\n description: _lt(`Rounds number up to nearest multiple of factor.`),\n args: args(`\n value (number) ${_lt(\"The value to round up to the nearest integer multiple of factor.\")}\n factor (number, default=${DEFAULT_FACTOR}) ${_lt(\"The number to whose multiples value will be rounded.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (value) => value === null || value === void 0 ? void 0 : value.format,\n compute: function (value, factor = DEFAULT_FACTOR) {\n const _value = toNumber(value);\n const _factor = toNumber(factor);\n assert(() => _factor >= 0 || _value <= 0, _lt(\"The factor (%s) must be positive when the value (%s) is positive.\", _factor.toString(), _value.toString()));\n return _factor ? Math.ceil(_value / _factor) * _factor : 0;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // CEILING.MATH\n // -----------------------------------------------------------------------------\n const CEILING_MATH = {\n description: _lt(`Rounds number up to nearest multiple of factor.`),\n args: args(`\n number (number) ${_lt(\"The value to round up to the nearest integer multiple of significance.\")}\n significance (number, default=${DEFAULT_SIGNIFICANCE}) ${_lt(\"The number to whose multiples number will be rounded. The sign of significance will be ignored.\")}\n mode (number, default=${DEFAULT_MODE}) ${_lt(\"If number is negative, specifies the rounding direction. If 0 or blank, it is rounded towards zero. Otherwise, it is rounded away from zero.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (number) => number === null || number === void 0 ? void 0 : number.format,\n compute: function (number, significance = DEFAULT_SIGNIFICANCE, mode = DEFAULT_MODE) {\n let _significance = toNumber(significance);\n if (_significance === 0) {\n return 0;\n }\n const _number = toNumber(number);\n _significance = Math.abs(_significance);\n if (_number >= 0) {\n return Math.ceil(_number / _significance) * _significance;\n }\n const _mode = toNumber(mode);\n if (_mode === 0) {\n return -Math.floor(Math.abs(_number) / _significance) * _significance;\n }\n return -Math.ceil(Math.abs(_number) / _significance) * _significance;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // CEILING.PRECISE\n // -----------------------------------------------------------------------------\n const CEILING_PRECISE = {\n description: _lt(`Rounds number up to nearest multiple of factor.`),\n args: args(`\n number (number) ${_lt(\"The value to round up to the nearest integer multiple of significance.\")}\n significance (number, default=${DEFAULT_SIGNIFICANCE}) ${_lt(\"The number to whose multiples number will be rounded.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (number) => number === null || number === void 0 ? void 0 : number.format,\n compute: function (number, significance) {\n return CEILING_MATH.compute(number, significance, 0);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COS\n // -----------------------------------------------------------------------------\n const COS = {\n description: _lt(\"Cosine of an angle provided in radians.\"),\n args: args(`\n angle (number) ${_lt(\"The angle to find the cosine of, in radians.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (angle) {\n return Math.cos(toNumber(angle));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COSH\n // -----------------------------------------------------------------------------\n const COSH = {\n description: _lt(\"Hyperbolic cosine of any real number.\"),\n args: args(`\n value (number) ${_lt(\"Any real value to calculate the hyperbolic cosine of.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (value) {\n return Math.cosh(toNumber(value));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COT\n // -----------------------------------------------------------------------------\n const COT = {\n description: _lt(\"Cotangent of an angle provided in radians.\"),\n args: args(`\n angle (number) ${_lt(\"The angle to find the cotangent of, in radians.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (angle) {\n const _angle = toNumber(angle);\n assert(() => _angle !== 0, _lt(`Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error.`));\n return 1 / Math.tan(_angle);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COTH\n // -----------------------------------------------------------------------------\n const COTH = {\n description: _lt(\"Hyperbolic cotangent of any real number.\"),\n args: args(`\n value (number) ${_lt(\"Any real value to calculate the hyperbolic cotangent of.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (value) {\n const _value = toNumber(value);\n assert(() => _value !== 0, _lt(`Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error.`));\n return 1 / Math.tanh(_value);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COUNTBLANK\n // -----------------------------------------------------------------------------\n const COUNTBLANK = {\n description: _lt(\"Number of empty values.\"),\n args: args(`\n value1 (any, range) ${_lt(\"The first value or range in which to count the number of blanks.\")}\n value2 (any, range, repeating) ${_lt(\"Additional values or ranges in which to count the number of blanks.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...argsValues) {\n return reduceAny(argsValues, (acc, a) => (a === null || a === undefined || a === \"\" ? acc + 1 : acc), 0);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COUNTIF\n // -----------------------------------------------------------------------------\n const COUNTIF = {\n description: _lt(\"A conditional count across a range.\"),\n args: args(`\n range (range) ${_lt(\"The range that is tested against criterion.\")}\n criterion (string) ${_lt(\"The pattern or test to apply to range.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...argsValues) {\n let count = 0;\n visitMatchingRanges(argsValues, (i, j) => {\n count += 1;\n });\n return count;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COUNTIFS\n // -----------------------------------------------------------------------------\n const COUNTIFS = {\n description: _lt(\"Count values depending on multiple criteria.\"),\n args: args(`\n criteria_range1 (range) ${_lt(\"The range to check against criterion1.\")}\n criterion1 (string) ${_lt(\"The pattern or test to apply to criteria_range1.\")}\n criteria_range2 (any, range, repeating) ${_lt(\"Additional ranges over which to evaluate the additional criteria. The filtered set will be the intersection of the sets produced by each criterion-range pair.\")}\n criterion2 (string, repeating) ${_lt(\"Additional criteria to check.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...argsValues) {\n let count = 0;\n visitMatchingRanges(argsValues, (i, j) => {\n count += 1;\n });\n return count;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COUNTUNIQUE\n // -----------------------------------------------------------------------------\n function isDefined(value) {\n switch (value) {\n case undefined:\n return false;\n case \"\":\n return false;\n case null:\n return false;\n default:\n return true;\n }\n }\n const COUNTUNIQUE = {\n description: _lt(\"Counts number of unique values in a range.\"),\n args: args(`\n value1 (any, range) ${_lt(\"The first value or range to consider for uniqueness.\")}\n value2 (any, range, repeating) ${_lt(\"Additional values or ranges to consider for uniqueness.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...argsValues) {\n return reduceAny(argsValues, (acc, a) => (isDefined(a) ? acc.add(a) : acc), new Set()).size;\n },\n };\n // -----------------------------------------------------------------------------\n // COUNTUNIQUEIFS\n // -----------------------------------------------------------------------------\n const COUNTUNIQUEIFS = {\n description: _lt(\"Counts number of unique values in a range, filtered by a set of criteria.\"),\n args: args(`\n range (range) ${_lt(\"The range of cells from which the number of unique values will be counted.\")}\n criteria_range1 (range) ${_lt(\"The range of cells over which to evaluate criterion1.\")}\n criterion1 (string) ${_lt(\"The pattern or test to apply to criteria_range1, such that each cell that evaluates to TRUE will be included in the filtered set.\")}\n criteria_range2 (any, range, repeating) ${_lt(\"Additional ranges over which to evaluate the additional criteria. The filtered set will be the intersection of the sets produced by each criterion-range pair.\")}\n criterion2 (string, repeating) ${_lt(\"The pattern or test to apply to criteria_range2.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (range, ...argsValues) {\n let uniqueValues = new Set();\n visitMatchingRanges(argsValues, (i, j) => {\n const value = range[i][j];\n if (isDefined(value)) {\n uniqueValues.add(value);\n }\n });\n return uniqueValues.size;\n },\n };\n // -----------------------------------------------------------------------------\n // CSC\n // -----------------------------------------------------------------------------\n const CSC = {\n description: _lt(\"Cosecant of an angle provided in radians.\"),\n args: args(`\n angle (number) ${_lt(\"The angle to find the cosecant of, in radians.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (angle) {\n const _angle = toNumber(angle);\n assert(() => _angle !== 0, _lt(`Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error.`));\n return 1 / Math.sin(_angle);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // CSCH\n // -----------------------------------------------------------------------------\n const CSCH = {\n description: _lt(\"Hyperbolic cosecant of any real number.\"),\n args: args(`\n value (number) ${_lt(\"Any real value to calculate the hyperbolic cosecant of.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (value) {\n const _value = toNumber(value);\n assert(() => _value !== 0, _lt(`Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error.`));\n return 1 / Math.sinh(_value);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DECIMAL\n // -----------------------------------------------------------------------------\n const DECIMAL = {\n description: _lt(\"Converts from another base to decimal.\"),\n args: args(`\n value (string) ${_lt(\"The number to convert.\")},\n base (number) ${_lt(\"The base to convert the value from.\")},\n `),\n returns: [\"NUMBER\"],\n compute: function (value, base) {\n let _base = toNumber(base);\n _base = Math.floor(_base);\n assert(() => 2 <= _base && _base <= 36, _lt(\"The base (%s) must be between 2 and 36 inclusive.\", _base.toString()));\n const _value = toString(value);\n if (_value === \"\") {\n return 0;\n }\n /**\n * @compatibility: on Google sheets, expects the parameter 'value' to be positive.\n * Return error if 'value' is positive.\n * Remove '-?' in the next regex to catch this error.\n */\n assert(() => !!_value.match(/^-?[a-z0-9]+$/i), _lt(\"The value (%s) must be a valid base %s representation.\", _value, _base.toString()));\n const deci = parseInt(_value, _base);\n assert(() => !isNaN(deci), _lt(\"The value (%s) must be a valid base %s representation.\", _value, _base.toString()));\n return deci;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DEGREES\n // -----------------------------------------------------------------------------\n const DEGREES = {\n description: _lt(`Converts an angle value in radians to degrees.`),\n args: args(`\n angle (number) ${_lt(\"The angle to convert from radians to degrees.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (angle) {\n return (toNumber(angle) * 180) / Math.PI;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // EXP\n // -----------------------------------------------------------------------------\n const EXP = {\n description: _lt(`Euler's number, e (~2.718) raised to a power.`),\n args: args(`\n value (number) ${_lt(\"The exponent to raise e.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (value) {\n return Math.exp(toNumber(value));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // FLOOR\n // -----------------------------------------------------------------------------\n const FLOOR = {\n description: _lt(`Rounds number down to nearest multiple of factor.`),\n args: args(`\n value (number) ${_lt(\"The value to round down to the nearest integer multiple of factor.\")}\n factor (number, default=${DEFAULT_FACTOR}) ${_lt(\"The number to whose multiples value will be rounded.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (value) => value === null || value === void 0 ? void 0 : value.format,\n compute: function (value, factor = DEFAULT_FACTOR) {\n const _value = toNumber(value);\n const _factor = toNumber(factor);\n assert(() => _factor >= 0 || _value <= 0, _lt(\"The factor (%s) must be positive when the value (%s) is positive.\", _factor.toString(), _value.toString()));\n return _factor ? Math.floor(_value / _factor) * _factor : 0;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // FLOOR.MATH\n // -----------------------------------------------------------------------------\n const FLOOR_MATH = {\n description: _lt(`Rounds number down to nearest multiple of factor.`),\n args: args(`\n number (number) ${_lt(\"The value to round down to the nearest integer multiple of significance.\")}\n significance (number, default=${DEFAULT_SIGNIFICANCE}) ${_lt(\"The number to whose multiples number will be rounded. The sign of significance will be ignored.\")}\n mode (number, default=${DEFAULT_MODE}) ${_lt(\"If number is negative, specifies the rounding direction. If 0 or blank, it is rounded away from zero. Otherwise, it is rounded towards zero.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (number) => number === null || number === void 0 ? void 0 : number.format,\n compute: function (number, significance = DEFAULT_SIGNIFICANCE, mode = DEFAULT_MODE) {\n let _significance = toNumber(significance);\n if (_significance === 0) {\n return 0;\n }\n const _number = toNumber(number);\n _significance = Math.abs(_significance);\n if (_number >= 0) {\n return Math.floor(_number / _significance) * _significance;\n }\n const _mode = toNumber(mode);\n if (_mode === 0) {\n return -Math.ceil(Math.abs(_number) / _significance) * _significance;\n }\n return -Math.floor(Math.abs(_number) / _significance) * _significance;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // FLOOR.PRECISE\n // -----------------------------------------------------------------------------\n const FLOOR_PRECISE = {\n description: _lt(`Rounds number down to nearest multiple of factor.`),\n args: args(`\n number (number) ${_lt(\"The value to round down to the nearest integer multiple of significance.\")}\n significance (number, default=${DEFAULT_SIGNIFICANCE}) ${_lt(\"The number to whose multiples number will be rounded.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (number) => number === null || number === void 0 ? void 0 : number.format,\n compute: function (number, significance = DEFAULT_SIGNIFICANCE) {\n return FLOOR_MATH.compute(number, significance, 0);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ISEVEN\n // -----------------------------------------------------------------------------\n const ISEVEN = {\n description: _lt(`Whether the provided value is even.`),\n args: args(`\n value (number) ${_lt(\"The value to be verified as even.\")}\n `),\n returns: [\"BOOLEAN\"],\n compute: function (value) {\n const _value = strictToNumber(value);\n return Math.floor(Math.abs(_value)) & 1 ? false : true;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ISO.CEILING\n // -----------------------------------------------------------------------------\n const ISO_CEILING = {\n description: _lt(`Rounds number up to nearest multiple of factor.`),\n args: args(`\n number (number) ${_lt(\"The value to round up to the nearest integer multiple of significance.\")}\n significance (number, default=${DEFAULT_SIGNIFICANCE}) ${_lt(\"The number to whose multiples number will be rounded.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (number) => number === null || number === void 0 ? void 0 : number.format,\n compute: function (number, significance = DEFAULT_SIGNIFICANCE) {\n return CEILING_MATH.compute(number, significance, 0);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ISODD\n // -----------------------------------------------------------------------------\n const ISODD = {\n description: _lt(`Whether the provided value is even.`),\n args: args(`\n value (number) ${_lt(\"The value to be verified as even.\")}\n `),\n returns: [\"BOOLEAN\"],\n compute: function (value) {\n const _value = strictToNumber(value);\n return Math.floor(Math.abs(_value)) & 1 ? true : false;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // LN\n // -----------------------------------------------------------------------------\n const LN = {\n description: _lt(`The logarithm of a number, base e (euler's number).`),\n args: args(`\n value (number) ${_lt(\"The value for which to calculate the logarithm, base e.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (value) {\n const _value = toNumber(value);\n assert(() => _value > 0, _lt(\"The value (%s) must be strictly positive.\", _value.toString()));\n return Math.log(_value);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // MOD\n // -----------------------------------------------------------------------------\n const MOD = {\n description: _lt(`Modulo (remainder) operator.`),\n args: args(`\n dividend (number) ${_lt(\"The number to be divided to find the remainder.\")}\n divisor (number) ${_lt(\"The number to divide by.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (dividend) => dividend === null || dividend === void 0 ? void 0 : dividend.format,\n compute: function (dividend, divisor) {\n const _divisor = toNumber(divisor);\n assert(() => _divisor !== 0, _lt(\"The divisor must be different from 0.\"));\n const _dividend = toNumber(dividend);\n const modulus = _dividend % _divisor;\n // -42 % 10 = -2 but we want 8, so need the code below\n if ((modulus > 0 && _divisor < 0) || (modulus < 0 && _divisor > 0)) {\n return modulus + _divisor;\n }\n return modulus;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ODD\n // -----------------------------------------------------------------------------\n const ODD = {\n description: _lt(`Rounds a number up to the nearest odd integer.`),\n args: args(`\n value (number) ${_lt(\"The value to round to the next greatest odd number.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (number) => number === null || number === void 0 ? void 0 : number.format,\n compute: function (value) {\n const _value = toNumber(value);\n let temp = Math.ceil(Math.abs(_value));\n temp = temp & 1 ? temp : temp + 1;\n return _value < 0 ? -temp : temp;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // PI\n // -----------------------------------------------------------------------------\n const PI = {\n description: _lt(`The number pi.`),\n args: [],\n returns: [\"NUMBER\"],\n compute: function () {\n return Math.PI;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // POWER\n // -----------------------------------------------------------------------------\n const POWER = {\n description: _lt(`A number raised to a power.`),\n args: args(`\n base (number) ${_lt(\"The number to raise to the exponent power.\")}\n exponent (number) ${_lt(\"The exponent to raise base to.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (base) => base === null || base === void 0 ? void 0 : base.format,\n compute: function (base, exponent) {\n const _base = toNumber(base);\n const _exponent = toNumber(exponent);\n assert(() => _base >= 0 || Number.isInteger(_exponent), _lt(\"The exponent (%s) must be an integer when the base is negative.\", _exponent.toString()));\n return Math.pow(_base, _exponent);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // PRODUCT\n // -----------------------------------------------------------------------------\n const PRODUCT = {\n description: _lt(\"Result of multiplying a series of numbers together.\"),\n args: args(`\n factor1 (number, range) ${_lt(\"The first number or range to calculate for the product.\")}\n factor2 (number, range, repeating) ${_lt(\"More numbers or ranges to calculate for the product.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (factor1) => {\n var _a;\n return Array.isArray(factor1) ? (_a = factor1[0][0]) === null || _a === void 0 ? void 0 : _a.format : factor1 === null || factor1 === void 0 ? void 0 : factor1.format;\n },\n compute: function (...factors) {\n let count = 0;\n let acc = 1;\n for (let n of factors) {\n if (Array.isArray(n)) {\n for (let i of n) {\n for (let j of i) {\n if (typeof j === \"number\") {\n acc *= j;\n count += 1;\n }\n }\n }\n }\n else if (n !== null && n !== undefined) {\n acc *= strictToNumber(n);\n count += 1;\n }\n }\n if (count === 0) {\n return 0;\n }\n return acc;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // RAND\n // -----------------------------------------------------------------------------\n const RAND = {\n description: _lt(\"A random number between 0 inclusive and 1 exclusive.\"),\n args: [],\n returns: [\"NUMBER\"],\n compute: function () {\n return Math.random();\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // RANDBETWEEN\n // -----------------------------------------------------------------------------\n const RANDBETWEEN = {\n description: _lt(\"Random integer between two values, inclusive.\"),\n args: args(`\n low (number) ${_lt(\"The low end of the random range.\")}\n high (number) ${_lt(\"The high end of the random range.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (low) => low === null || low === void 0 ? void 0 : low.format,\n compute: function (low, high) {\n let _low = toNumber(low);\n if (!Number.isInteger(_low)) {\n _low = Math.ceil(_low);\n }\n let _high = toNumber(high);\n if (!Number.isInteger(_high)) {\n _high = Math.floor(_high);\n }\n assert(() => _low <= _high, _lt(\"The high (%s) must be greater than or equal to the low (%s).\", _high.toString(), _low.toString()));\n return _low + Math.ceil((_high - _low + 1) * Math.random()) - 1;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ROUND\n // -----------------------------------------------------------------------------\n const ROUND = {\n description: _lt(\"Rounds a number according to standard rules.\"),\n args: args(`\n value (number) ${_lt(\"The value to round to places number of places.\")}\n places (number, default=${DEFAULT_PLACES}) ${_lt(\"The number of decimal places to which to round.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (value) => value === null || value === void 0 ? void 0 : value.format,\n compute: function (value, places = DEFAULT_PLACES) {\n const _value = toNumber(value);\n let _places = toNumber(places);\n const absValue = Math.abs(_value);\n let tempResult;\n if (_places === 0) {\n tempResult = Math.round(absValue);\n }\n else {\n if (!Number.isInteger(_places)) {\n _places = Math.trunc(_places);\n }\n tempResult = Math.round(absValue * Math.pow(10, _places)) / Math.pow(10, _places);\n }\n return _value >= 0 ? tempResult : -tempResult;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ROUNDDOWN\n // -----------------------------------------------------------------------------\n const ROUNDDOWN = {\n description: _lt(`Rounds down a number.`),\n args: args(`\n value (number) ${_lt(\"The value to round to places number of places, always rounding down.\")}\n places (number, default=${DEFAULT_PLACES}) ${_lt(\"The number of decimal places to which to round.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (value) => value === null || value === void 0 ? void 0 : value.format,\n compute: function (value, places = DEFAULT_PLACES) {\n const _value = toNumber(value);\n let _places = toNumber(places);\n const absValue = Math.abs(_value);\n let tempResult;\n if (_places === 0) {\n tempResult = Math.floor(absValue);\n }\n else {\n if (!Number.isInteger(_places)) {\n _places = Math.trunc(_places);\n }\n tempResult = Math.floor(absValue * Math.pow(10, _places)) / Math.pow(10, _places);\n }\n return _value >= 0 ? tempResult : -tempResult;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ROUNDUP\n // -----------------------------------------------------------------------------\n const ROUNDUP = {\n description: _lt(`Rounds up a number.`),\n args: args(`\n value (number) ${_lt(\"The value to round to places number of places, always rounding up.\")}\n places (number, default=${DEFAULT_PLACES}) ${_lt(\"The number of decimal places to which to round.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (value) => value === null || value === void 0 ? void 0 : value.format,\n compute: function (value, places = DEFAULT_PLACES) {\n const _value = toNumber(value);\n let _places = toNumber(places);\n const absValue = Math.abs(_value);\n let tempResult;\n if (_places === 0) {\n tempResult = Math.ceil(absValue);\n }\n else {\n if (!Number.isInteger(_places)) {\n _places = Math.trunc(_places);\n }\n tempResult = Math.ceil(absValue * Math.pow(10, _places)) / Math.pow(10, _places);\n }\n return _value >= 0 ? tempResult : -tempResult;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // SEC\n // -----------------------------------------------------------------------------\n const SEC = {\n description: _lt(\"Secant of an angle provided in radians.\"),\n args: args(`\n angle (number) ${_lt(\"The angle to find the secant of, in radians.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (angle) {\n return 1 / Math.cos(toNumber(angle));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // SECH\n // -----------------------------------------------------------------------------\n const SECH = {\n description: _lt(\"Hyperbolic secant of any real number.\"),\n args: args(`\n value (number) ${_lt(\"Any real value to calculate the hyperbolic secant of.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (value) {\n return 1 / Math.cosh(toNumber(value));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // SIN\n // -----------------------------------------------------------------------------\n const SIN = {\n description: _lt(\"Sine of an angle provided in radians.\"),\n args: args(`\n angle (number) ${_lt(\"The angle to find the sine of, in radians.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (angle) {\n return Math.sin(toNumber(angle));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // SINH\n // -----------------------------------------------------------------------------\n const SINH = {\n description: _lt(\"Hyperbolic sine of any real number.\"),\n args: args(`\n value (number) ${_lt(\"Any real value to calculate the hyperbolic sine of.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (value) {\n return Math.sinh(toNumber(value));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // SQRT\n // -----------------------------------------------------------------------------\n const SQRT = {\n description: _lt(\"Positive square root of a positive number.\"),\n args: args(`\n value (number) ${_lt(\"The number for which to calculate the positive square root.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (value) => value === null || value === void 0 ? void 0 : value.format,\n compute: function (value) {\n const _value = toNumber(value);\n assert(() => _value >= 0, _lt(\"The value (%s) must be positive or null.\", _value.toString()));\n return Math.sqrt(_value);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // SUM\n // -----------------------------------------------------------------------------\n const SUM = {\n description: _lt(\"Sum of a series of numbers and/or cells.\"),\n args: args(`\n value1 (number, range) ${_lt(\"The first number or range to add together.\")}\n value2 (number, range, repeating) ${_lt(\"Additional numbers or ranges to add to value1.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (value1) => {\n var _a;\n return Array.isArray(value1) ? (_a = value1[0][0]) === null || _a === void 0 ? void 0 : _a.format : value1 === null || value1 === void 0 ? void 0 : value1.format;\n },\n compute: function (...values) {\n return reduceNumbers(values, (acc, a) => acc + a, 0);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // SUMIF\n // -----------------------------------------------------------------------------\n const SUMIF = {\n description: _lt(\"A conditional sum across a range.\"),\n args: args(`\n criteria_range (range) ${_lt(\"The range which is tested against criterion.\")}\n criterion (string) ${_lt(\"The pattern or test to apply to range.\")}\n sum_range (range, default=criteria_range) ${_lt(\"The range to be summed, if different from range.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (criteriaRange, criterion, sumRange = undefined) {\n if (sumRange === undefined) {\n sumRange = criteriaRange;\n }\n let sum = 0;\n visitMatchingRanges([criteriaRange, criterion], (i, j) => {\n const value = sumRange[i][j];\n if (typeof value === \"number\") {\n sum += value;\n }\n });\n return sum;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // SUMIFS\n // -----------------------------------------------------------------------------\n const SUMIFS = {\n description: _lt(\"Sums a range depending on multiple criteria.\"),\n args: args(`\n sum_range (range) ${_lt(\"The range to sum.\")}\n criteria_range1 (range) ${_lt(\"The range to check against criterion1.\")}\n criterion1 (string) ${_lt(\"The pattern or test to apply to criteria_range1.\")}\n criteria_range2 (any, range, repeating) ${_lt(\"Additional ranges to check.\")}\n criterion2 (string, repeating) ${_lt(\"Additional criteria to check.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (sumRange, ...criters) {\n let sum = 0;\n visitMatchingRanges(criters, (i, j) => {\n const value = sumRange[i][j];\n if (typeof value === \"number\") {\n sum += value;\n }\n });\n return sum;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // TAN\n // -----------------------------------------------------------------------------\n const TAN = {\n description: _lt(\"Tangent of an angle provided in radians.\"),\n args: args(`\n angle (number) ${_lt(\"The angle to find the tangent of, in radians.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (angle) {\n return Math.tan(toNumber(angle));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // TANH\n // -----------------------------------------------------------------------------\n const TANH = {\n description: _lt(\"Hyperbolic tangent of any real number.\"),\n args: args(`\n value (number) ${_lt(\"Any real value to calculate the hyperbolic tangent of.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (value) {\n return Math.tanh(toNumber(value));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // TRUNC\n // -----------------------------------------------------------------------------\n const TRUNC = {\n description: _lt(\"Truncates a number.\"),\n args: args(`\n value (number) ${_lt(\"The value to be truncated.\")}\n places (number, default=${DEFAULT_PLACES}) ${_lt(\"The number of significant digits to the right of the decimal point to retain.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (value) => value === null || value === void 0 ? void 0 : value.format,\n compute: function (value, places = DEFAULT_PLACES) {\n const _value = toNumber(value);\n let _places = toNumber(places);\n if (_places === 0) {\n return Math.trunc(_value);\n }\n if (!Number.isInteger(_places)) {\n _places = Math.trunc(_places);\n }\n return Math.trunc(_value * Math.pow(10, _places)) / Math.pow(10, _places);\n },\n isExported: true,\n };\n\n var math = /*#__PURE__*/Object.freeze({\n __proto__: null,\n ABS: ABS,\n ACOS: ACOS,\n ACOSH: ACOSH,\n ACOT: ACOT,\n ACOTH: ACOTH,\n ASIN: ASIN,\n ASINH: ASINH,\n ATAN: ATAN,\n ATAN2: ATAN2,\n ATANH: ATANH,\n CEILING: CEILING,\n CEILING_MATH: CEILING_MATH,\n CEILING_PRECISE: CEILING_PRECISE,\n COS: COS,\n COSH: COSH,\n COT: COT,\n COTH: COTH,\n COUNTBLANK: COUNTBLANK,\n COUNTIF: COUNTIF,\n COUNTIFS: COUNTIFS,\n COUNTUNIQUE: COUNTUNIQUE,\n COUNTUNIQUEIFS: COUNTUNIQUEIFS,\n CSC: CSC,\n CSCH: CSCH,\n DECIMAL: DECIMAL,\n DEGREES: DEGREES,\n EXP: EXP,\n FLOOR: FLOOR,\n FLOOR_MATH: FLOOR_MATH,\n FLOOR_PRECISE: FLOOR_PRECISE,\n ISEVEN: ISEVEN,\n ISO_CEILING: ISO_CEILING,\n ISODD: ISODD,\n LN: LN,\n MOD: MOD,\n ODD: ODD,\n PI: PI,\n POWER: POWER,\n PRODUCT: PRODUCT,\n RAND: RAND,\n RANDBETWEEN: RANDBETWEEN,\n ROUND: ROUND,\n ROUNDDOWN: ROUNDDOWN,\n ROUNDUP: ROUNDUP,\n SEC: SEC,\n SECH: SECH,\n SIN: SIN,\n SINH: SINH,\n SQRT: SQRT,\n SUM: SUM,\n SUMIF: SUMIF,\n SUMIFS: SUMIFS,\n TAN: TAN,\n TANH: TANH,\n TRUNC: TRUNC\n });\n\n // Note: dataY and dataX may not have the same dimension\n function covariance(dataY, dataX, isSample) {\n let flatDataY = [];\n let flatDataX = [];\n let lenY = 0;\n let lenX = 0;\n visitAny([dataY], (y) => {\n flatDataY.push(y);\n lenY += 1;\n });\n visitAny([dataX], (x) => {\n flatDataX.push(x);\n lenX += 1;\n });\n assert(() => lenY === lenX, _lt(\"[[FUNCTION_NAME]] has mismatched argument count %s vs %s.\", lenY.toString(), lenX.toString()));\n let count = 0;\n let sumY = 0;\n let sumX = 0;\n for (let i = 0; i < lenY; i++) {\n const valueY = flatDataY[i];\n const valueX = flatDataX[i];\n if (typeof valueY === \"number\" && typeof valueX === \"number\") {\n count += 1;\n sumY += valueY;\n sumX += valueX;\n }\n }\n assert(() => count !== 0 && (!isSample || count !== 1), _lt(`Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error.`));\n const averageY = sumY / count;\n const averageX = sumX / count;\n let acc = 0;\n for (let i = 0; i < lenY; i++) {\n const valueY = flatDataY[i];\n const valueX = flatDataX[i];\n if (typeof valueY === \"number\" && typeof valueX === \"number\") {\n acc += (valueY - averageY) * (valueX - averageX);\n }\n }\n return acc / (count - (isSample ? 1 : 0));\n }\n function variance(args, isSample, textAs0) {\n let count = 0;\n let sum = 0;\n const reduceFunction = textAs0 ? reduceNumbersTextAs0 : reduceNumbers;\n sum = reduceFunction(args, (acc, a) => {\n count += 1;\n return acc + a;\n }, 0);\n assert(() => count !== 0 && (!isSample || count !== 1), _lt(`Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error.`));\n const average = sum / count;\n return (reduceFunction(args, (acc, a) => acc + Math.pow(a - average, 2), 0) /\n (count - (isSample ? 1 : 0)));\n }\n function centile(data, percent, isInclusive) {\n const _percent = toNumber(percent);\n assert(() => (isInclusive ? 0 <= _percent && _percent <= 1 : 0 < _percent && _percent < 1), _lt(`Function [[FUNCTION_NAME]] parameter 2 value is out of range.`));\n let sortedArray = [];\n let index;\n let count = 0;\n visitAny(data, (d) => {\n if (typeof d === \"number\") {\n index = dichotomicSearch(sortedArray, d, \"nextSmaller\", \"asc\", sortedArray.length, (array, i) => array[i]);\n sortedArray.splice(index + 1, 0, d);\n count++;\n }\n });\n assert(() => count !== 0, _lt(`[[FUNCTION_NAME]] has no valid input data.`));\n if (!isInclusive) {\n // 2nd argument must be between 1/(n+1) and n/(n+1) with n the number of data\n assert(() => 1 / (count + 1) <= _percent && _percent <= count / (count + 1), _lt(`Function [[FUNCTION_NAME]] parameter 2 value is out of range.`));\n }\n return percentile(sortedArray, _percent, isInclusive);\n }\n // -----------------------------------------------------------------------------\n // AVEDEV\n // -----------------------------------------------------------------------------\n const AVEDEV = {\n description: _lt(\"Average magnitude of deviations from mean.\"),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range of the sample.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to include in the sample.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...values) {\n let count = 0;\n const sum = reduceNumbers(values, (acc, a) => {\n count += 1;\n return acc + a;\n }, 0);\n assert(() => count !== 0, _lt(`Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error.`));\n const average = sum / count;\n return reduceNumbers(values, (acc, a) => acc + Math.abs(average - a), 0) / count;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // AVERAGE\n // -----------------------------------------------------------------------------\n const AVERAGE = {\n description: _lt(`Numerical average value in a dataset, ignoring text.`),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range to consider when calculating the average value.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to consider when calculating the average value.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (value1) => {\n var _a;\n return Array.isArray(value1) ? (_a = value1[0][0]) === null || _a === void 0 ? void 0 : _a.format : value1 === null || value1 === void 0 ? void 0 : value1.format;\n },\n compute: function (...values) {\n let count = 0;\n const sum = reduceNumbers(values, (acc, a) => {\n count += 1;\n return acc + a;\n }, 0);\n assert(() => count !== 0, _lt(`Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error.`));\n return sum / count;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // AVERAGE.WEIGHTED\n // -----------------------------------------------------------------------------\n const rangeError = _lt(`[[FUNCTION_NAME]] has mismatched range sizes.`);\n const negativeWeightError = _lt(`[[FUNCTION_NAME]] expects the weight to be positive or equal to 0.`);\n const AVERAGE_WEIGHTED = {\n description: _lt(`Weighted average.`),\n args: args(`\n values (number, range) ${_lt(\"Values to average.\")}\n weights (number, range) ${_lt(\"Weights for each corresponding value.\")}\n additional_values (number, range, repeating) ${_lt(\"Additional values to average.\")}\n additional_weights (number, range, repeating) ${_lt(\"Additional weights.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (values) => {\n var _a;\n return Array.isArray(values) ? (_a = values[0][0]) === null || _a === void 0 ? void 0 : _a.format : values === null || values === void 0 ? void 0 : values.format;\n },\n compute: function (...values) {\n let sum = 0;\n let count = 0;\n let value;\n let weight;\n assert(() => values.length % 2 === 0, _lt(`Wrong number of Argument[]. Expected an even number of Argument[].`));\n for (let n = 0; n < values.length - 1; n += 2) {\n value = values[n];\n weight = values[n + 1];\n // if (typeof value != typeof weight) {\n // throw new Error(rangeError);\n // }\n if (Array.isArray(value)) {\n assert(() => Array.isArray(weight), rangeError);\n let dimColValue = value.length;\n let dimLinValue = value[0].length;\n assert(() => dimColValue === weight.length && dimLinValue === weight[0].length, rangeError);\n for (let i = 0; i < dimColValue; i++) {\n for (let j = 0; j < dimLinValue; j++) {\n let subValue = value[i][j];\n let subWeight = weight[i][j];\n let subValueIsNumber = typeof subValue === \"number\";\n let subWeightIsNumber = typeof subWeight === \"number\";\n // typeof subValue or subWeight can be 'number' or 'undefined'\n assert(() => subValueIsNumber === subWeightIsNumber, _lt(`[[FUNCTION_NAME]] expects number values.`));\n if (subWeightIsNumber) {\n assert(() => subWeight >= 0, negativeWeightError);\n sum += subValue * subWeight;\n count += subWeight;\n }\n }\n }\n }\n else {\n weight = toNumber(weight);\n value = toNumber(value);\n assert(() => weight >= 0, negativeWeightError);\n sum += value * weight;\n count += weight;\n }\n }\n assert(() => count !== 0, _lt(`Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error.`));\n return sum / count;\n },\n };\n // -----------------------------------------------------------------------------\n // AVERAGEA\n // -----------------------------------------------------------------------------\n const AVERAGEA = {\n description: _lt(`Numerical average value in a dataset.`),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range to consider when calculating the average value.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to consider when calculating the average value.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (value1) => {\n var _a;\n return Array.isArray(value1) ? (_a = value1[0][0]) === null || _a === void 0 ? void 0 : _a.format : value1 === null || value1 === void 0 ? void 0 : value1.format;\n },\n compute: function (...values) {\n let count = 0;\n const sum = reduceNumbersTextAs0(values, (acc, a) => {\n count += 1;\n return acc + a;\n }, 0);\n assert(() => count !== 0, _lt(`Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error.`));\n return sum / count;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // AVERAGEIF\n // -----------------------------------------------------------------------------\n const AVERAGEIF = {\n description: _lt(`Average of values depending on criteria.`),\n args: args(`\n criteria_range (range) ${_lt(\"The range to check against criterion.\")}\n criterion (string) ${_lt(\"The pattern or test to apply to criteria_range.\")}\n average_range (range, default=criteria_range) ${_lt(\"The range to average. If not included, criteria_range is used for the average instead.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (criteriaRange, criterion, averageRange) {\n if (averageRange === undefined || averageRange === null) {\n averageRange = criteriaRange;\n }\n let count = 0;\n let sum = 0;\n visitMatchingRanges([criteriaRange, criterion], (i, j) => {\n const value = (averageRange || criteriaRange)[i][j];\n if (typeof value === \"number\") {\n count += 1;\n sum += value;\n }\n });\n assert(() => count !== 0, _lt(`Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error.`));\n return sum / count;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // AVERAGEIFS\n // -----------------------------------------------------------------------------\n const AVERAGEIFS = {\n description: _lt(`Average of values depending on multiple criteria.`),\n args: args(`\n average_range (range) ${_lt(\"The range to average.\")}\n criteria_range1 (range) ${_lt(\"The range to check against criterion1.\")}\n criterion1 (string) ${_lt(\"The pattern or test to apply to criteria_range1.\")}\n criteria_range2 (any, range, repeating) ${_lt(\"Additional criteria_range and criterion to check.\")}\n criterion2 (string, repeating) ${_lt(\"The pattern or test to apply to criteria_range2.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (averageRange, ...values) {\n let count = 0;\n let sum = 0;\n visitMatchingRanges(values, (i, j) => {\n const value = averageRange[i][j];\n if (typeof value === \"number\") {\n count += 1;\n sum += value;\n }\n });\n assert(() => count !== 0, _lt(`Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error.`));\n return sum / count;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COUNT\n // -----------------------------------------------------------------------------\n const COUNT = {\n description: _lt(`The number of numeric values in dataset.`),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range to consider when counting.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to consider when counting.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...values) {\n let count = 0;\n for (let n of values) {\n if (Array.isArray(n)) {\n for (let i of n) {\n for (let j of i) {\n if (typeof j === \"number\") {\n count += 1;\n }\n }\n }\n }\n else if (typeof n !== \"string\" || isNumber(n) || parseDateTime(n)) {\n count += 1;\n }\n }\n return count;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COUNTA\n // -----------------------------------------------------------------------------\n const COUNTA = {\n description: _lt(`The number of values in a dataset.`),\n args: args(`\n value1 (any, range) ${_lt(\"The first value or range to consider when counting.\")}\n value2 (any, range, repeating) ${_lt(\"Additional values or ranges to consider when counting.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...values) {\n return reduceAny(values, (acc, a) => (a !== undefined && a !== null ? acc + 1 : acc), 0);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COVAR\n // -----------------------------------------------------------------------------\n // Note: Unlike the VAR function which corresponds to the variance over a sample (VAR.S),\n // the COVAR function corresponds to the covariance over an entire population (COVAR.P)\n const COVAR = {\n description: _lt(`The covariance of a dataset.`),\n args: args(`\n data_y (any, range) ${_lt(\"The range representing the array or matrix of dependent data.\")}\n data_x (any, range) ${_lt(\"The range representing the array or matrix of independent data.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (dataY, dataX) {\n return covariance(dataY, dataX, false);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COVARIANCE.P\n // -----------------------------------------------------------------------------\n const COVARIANCE_P = {\n description: _lt(`The covariance of a dataset.`),\n args: args(`\n data_y (any, range) ${_lt(\"The range representing the array or matrix of dependent data.\")}\n data_x (any, range) ${_lt(\"The range representing the array or matrix of independent data.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (dataY, dataX) {\n return covariance(dataY, dataX, false);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COVARIANCE.S\n // -----------------------------------------------------------------------------\n const COVARIANCE_S = {\n description: _lt(`The sample covariance of a dataset.`),\n args: args(`\n data_y (any, range) ${_lt(\"The range representing the array or matrix of dependent data.\")}\n data_x (any, range) ${_lt(\"The range representing the array or matrix of independent data.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (dataY, dataX) {\n return covariance(dataY, dataX, true);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // LARGE\n // -----------------------------------------------------------------------------\n const LARGE = {\n description: _lt(\"Nth largest element from a data set.\"),\n args: args(`\n data (any, range) ${_lt(\"Array or range containing the dataset to consider.\")}\n n (number) ${_lt(\"The rank from largest to smallest of the element to return.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (data) => {\n var _a;\n return Array.isArray(data) ? (_a = data[0][0]) === null || _a === void 0 ? void 0 : _a.format : data === null || data === void 0 ? void 0 : data.format;\n },\n compute: function (data, n) {\n const _n = Math.trunc(toNumber(n));\n let largests = [];\n let index;\n let count = 0;\n visitAny([data], (d) => {\n if (typeof d === \"number\") {\n index = dichotomicSearch(largests, d, \"nextSmaller\", \"asc\", largests.length, (array, i) => array[i]);\n largests.splice(index + 1, 0, d);\n count++;\n if (count > _n) {\n largests.shift();\n count--;\n }\n }\n });\n const result = largests.shift();\n assert(() => result !== undefined, _lt(`[[FUNCTION_NAME]] has no valid input data.`));\n assert(() => count >= _n, _lt(\"Function [[FUNCTION_NAME]] parameter 2 value (%s) is out of range.\", _n.toString()));\n return result;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // MAX\n // -----------------------------------------------------------------------------\n const MAX = {\n description: _lt(\"Maximum value in a numeric dataset.\"),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range to consider when calculating the maximum value.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to consider when calculating the maximum value.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (value1) => {\n var _a;\n return Array.isArray(value1) ? (_a = value1[0][0]) === null || _a === void 0 ? void 0 : _a.format : value1 === null || value1 === void 0 ? void 0 : value1.format;\n },\n compute: function (...values) {\n const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity);\n return result === -Infinity ? 0 : result;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // MAXA\n // -----------------------------------------------------------------------------\n const MAXA = {\n description: _lt(\"Maximum numeric value in a dataset.\"),\n args: args(`\n value1 (any, range) ${_lt(\"The first value or range to consider when calculating the maximum value.\")}\n value2 (any, range, repeating) ${_lt(\"Additional values or ranges to consider when calculating the maximum value.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (value1) => {\n var _a;\n return Array.isArray(value1) ? (_a = value1[0][0]) === null || _a === void 0 ? void 0 : _a.format : value1 === null || value1 === void 0 ? void 0 : value1.format;\n },\n compute: function (...values) {\n const maxa = reduceNumbersTextAs0(values, (acc, a) => {\n return Math.max(a, acc);\n }, -Infinity);\n return maxa === -Infinity ? 0 : maxa;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // MAXIFS\n // -----------------------------------------------------------------------------\n const MAXIFS = {\n description: _lt(\"Returns the maximum value in a range of cells, filtered by a set of criteria.\"),\n args: args(`\n range (range) ${_lt(\"The range of cells from which the maximum will be determined.\")}\n criteria_range1 (range) ${_lt(\"The range of cells over which to evaluate criterion1.\")}\n criterion1 (string) ${_lt(\"The pattern or test to apply to criteria_range1, such that each cell that evaluates to TRUE will be included in the filtered set.\")}\n criteria_range2 (any, range, repeating) ${_lt(\"Additional ranges over which to evaluate the additional criteria. The filtered set will be the intersection of the sets produced by each criterion-range pair.\")}\n criterion2 (string, repeating) ${_lt(\"The pattern or test to apply to criteria_range2.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (range, ...args) {\n let result = -Infinity;\n visitMatchingRanges(args, (i, j) => {\n const value = range[i][j];\n if (typeof value === \"number\") {\n result = result < value ? value : result;\n }\n });\n return result === -Infinity ? 0 : result;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // MEDIAN\n // -----------------------------------------------------------------------------\n const MEDIAN = {\n description: _lt(\"Median value in a numeric dataset.\"),\n args: args(`\n value1 (any, range) ${_lt(\"The first value or range to consider when calculating the median value.\")}\n value2 (any, range, repeating) ${_lt(\"Additional values or ranges to consider when calculating the median value.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (value1) => {\n var _a;\n return Array.isArray(value1) ? (_a = value1[0][0]) === null || _a === void 0 ? void 0 : _a.format : value1 === null || value1 === void 0 ? void 0 : value1.format;\n },\n compute: function (...values) {\n let data = [];\n visitNumbers(values, (arg) => {\n data.push(arg);\n });\n return centile(data, 0.5, true);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // MIN\n // -----------------------------------------------------------------------------\n const MIN = {\n description: _lt(\"Minimum value in a numeric dataset.\"),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range to consider when calculating the minimum value.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to consider when calculating the minimum value.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (value1) => {\n var _a;\n return Array.isArray(value1) ? (_a = value1[0][0]) === null || _a === void 0 ? void 0 : _a.format : value1 === null || value1 === void 0 ? void 0 : value1.format;\n },\n compute: function (...values) {\n const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity);\n return result === Infinity ? 0 : result;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // MINA\n // -----------------------------------------------------------------------------\n const MINA = {\n description: _lt(\"Minimum numeric value in a dataset.\"),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range to consider when calculating the minimum value.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to consider when calculating the minimum value.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (value1) => {\n var _a;\n return Array.isArray(value1) ? (_a = value1[0][0]) === null || _a === void 0 ? void 0 : _a.format : value1 === null || value1 === void 0 ? void 0 : value1.format;\n },\n compute: function (...values) {\n const mina = reduceNumbersTextAs0(values, (acc, a) => {\n return Math.min(a, acc);\n }, Infinity);\n return mina === Infinity ? 0 : mina;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // MINIFS\n // -----------------------------------------------------------------------------\n const MINIFS = {\n description: _lt(\"Returns the minimum value in a range of cells, filtered by a set of criteria.\"),\n args: args(`\n range (range) ${_lt(\"The range of cells from which the minimum will be determined.\")}\n criteria_range1 (range) ${_lt(\"The range of cells over which to evaluate criterion1.\")}\n criterion1 (string) ${_lt(\"The pattern or test to apply to criteria_range1, such that each cell that evaluates to TRUE will be included in the filtered set.\")}\n criteria_range2 (any, range, repeating) ${_lt(\"Additional ranges over which to evaluate the additional criteria. The filtered set will be the intersection of the sets produced by each criterion-range pair.\")}\n criterion2 (string, repeating) ${_lt(\"The pattern or test to apply to criteria_range2.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (range, ...args) {\n let result = Infinity;\n visitMatchingRanges(args, (i, j) => {\n const value = range[i][j];\n if (typeof value === \"number\") {\n result = result > value ? value : result;\n }\n });\n return result === Infinity ? 0 : result;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // PERCENTILE\n // -----------------------------------------------------------------------------\n const PERCENTILE = {\n description: _lt(\"Value at a given percentile of a dataset.\"),\n args: args(`\n data (any, range) ${_lt(\"The array or range containing the dataset to consider.\")}\n percentile (number) ${_lt(\"The percentile whose value within data will be calculated and returned.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (data) => {\n var _a;\n return Array.isArray(data) ? (_a = data[0][0]) === null || _a === void 0 ? void 0 : _a.format : data === null || data === void 0 ? void 0 : data.format;\n },\n compute: function (data, percentile) {\n return PERCENTILE_INC.compute(data, percentile);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // PERCENTILE.EXC\n // -----------------------------------------------------------------------------\n const PERCENTILE_EXC = {\n description: _lt(\"Value at a given percentile of a dataset exclusive of 0 and 1.\"),\n args: args(`\n data (any, range) ${_lt(\"The array or range containing the dataset to consider.\")}\n percentile (number) ${_lt(\"The percentile, exclusive of 0 and 1, whose value within 'data' will be calculated and returned.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (data) => {\n var _a;\n return Array.isArray(data) ? (_a = data[0][0]) === null || _a === void 0 ? void 0 : _a.format : data === null || data === void 0 ? void 0 : data.format;\n },\n compute: function (data, percentile) {\n return centile([data], percentile, false);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // PERCENTILE.INC\n // -----------------------------------------------------------------------------\n const PERCENTILE_INC = {\n description: _lt(\"Value at a given percentile of a dataset.\"),\n args: args(`\n data (any, range) ${_lt(\"The array or range containing the dataset to consider.\")}\n percentile (number) ${_lt(\"The percentile whose value within data will be calculated and returned.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (data) => {\n var _a;\n return Array.isArray(data) ? (_a = data[0][0]) === null || _a === void 0 ? void 0 : _a.format : data === null || data === void 0 ? void 0 : data.format;\n },\n compute: function (data, percentile) {\n return centile([data], percentile, true);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // QUARTILE\n // -----------------------------------------------------------------------------\n const QUARTILE = {\n description: _lt(\"Value nearest to a specific quartile of a dataset.\"),\n args: args(`\n data (any, range) ${_lt(\"The array or range containing the dataset to consider.\")}\n quartile_number (number) ${_lt(\"Which quartile value to return.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (data) => {\n var _a;\n return Array.isArray(data) ? (_a = data[0][0]) === null || _a === void 0 ? void 0 : _a.format : data === null || data === void 0 ? void 0 : data.format;\n },\n compute: function (data, quartileNumber) {\n return QUARTILE_INC.compute(data, quartileNumber);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // QUARTILE.EXC\n // -----------------------------------------------------------------------------\n const QUARTILE_EXC = {\n description: _lt(\"Value nearest to a specific quartile of a dataset exclusive of 0 and 4.\"),\n args: args(`\n data (any, range) ${_lt(\"The array or range containing the dataset to consider.\")}\n quartile_number (number) ${_lt(\"Which quartile value, exclusive of 0 and 4, to return.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (data) => {\n var _a;\n return Array.isArray(data) ? (_a = data[0][0]) === null || _a === void 0 ? void 0 : _a.format : data === null || data === void 0 ? void 0 : data.format;\n },\n compute: function (data, quartileNumber) {\n const _quartileNumber = Math.trunc(toNumber(quartileNumber));\n return centile([data], 0.25 * _quartileNumber, false);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // QUARTILE.INC\n // -----------------------------------------------------------------------------\n const QUARTILE_INC = {\n description: _lt(\"Value nearest to a specific quartile of a dataset.\"),\n args: args(`\n data (any, range) ${_lt(\"The array or range containing the dataset to consider.\")}\n quartile_number (number) ${_lt(\"Which quartile value to return.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (data) => {\n var _a;\n return Array.isArray(data) ? (_a = data[0][0]) === null || _a === void 0 ? void 0 : _a.format : data === null || data === void 0 ? void 0 : data.format;\n },\n compute: function (data, quartileNumber) {\n const _quartileNumber = Math.trunc(toNumber(quartileNumber));\n return centile([data], 0.25 * _quartileNumber, true);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // SMALL\n // -----------------------------------------------------------------------------\n const SMALL = {\n description: _lt(\"Nth smallest element in a data set.\"),\n args: args(`\n data (any, range) ${_lt(\"The array or range containing the dataset to consider.\")}\n n (number) ${_lt(\"The rank from smallest to largest of the element to return.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (data) => {\n var _a;\n return Array.isArray(data) ? (_a = data[0][0]) === null || _a === void 0 ? void 0 : _a.format : data === null || data === void 0 ? void 0 : data.format;\n },\n compute: function (data, n) {\n const _n = Math.trunc(toNumber(n));\n let largests = [];\n let index;\n let count = 0;\n visitAny([data], (d) => {\n if (typeof d === \"number\") {\n index = dichotomicSearch(largests, d, \"nextSmaller\", \"asc\", largests.length, (array, i) => array[i]);\n largests.splice(index + 1, 0, d);\n count++;\n if (count > _n) {\n largests.pop();\n count--;\n }\n }\n });\n const result = largests.pop();\n assert(() => result !== undefined, _lt(`[[FUNCTION_NAME]] has no valid input data.`));\n assert(() => count >= _n, _lt(\"Function [[FUNCTION_NAME]] parameter 2 value (%s) is out of range.\", _n.toString()));\n return result;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // STDEV\n // -----------------------------------------------------------------------------\n const STDEV = {\n description: _lt(\"Standard deviation.\"),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range of the sample.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to include in the sample.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...values) {\n return Math.sqrt(VAR.compute(...values));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // STDEV.P\n // -----------------------------------------------------------------------------\n const STDEV_P = {\n description: _lt(\"Standard deviation of entire population.\"),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range of the population.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to include in the population.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...values) {\n return Math.sqrt(VAR_P.compute(...values));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // STDEV.S\n // -----------------------------------------------------------------------------\n const STDEV_S = {\n description: _lt(\"Standard deviation.\"),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range of the sample.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to include in the sample.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...values) {\n return Math.sqrt(VAR_S.compute(...values));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // STDEVA\n // -----------------------------------------------------------------------------\n const STDEVA = {\n description: _lt(\"Standard deviation of sample (text as 0).\"),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range of the sample.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to include in the sample.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...values) {\n return Math.sqrt(VARA.compute(...values));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // STDEVP\n // -----------------------------------------------------------------------------\n const STDEVP = {\n description: _lt(\"Standard deviation of entire population.\"),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range of the population.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to include in the population.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...values) {\n return Math.sqrt(VARP.compute(...values));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // STDEVPA\n // -----------------------------------------------------------------------------\n const STDEVPA = {\n description: _lt(\"Standard deviation of entire population (text as 0).\"),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range of the population.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to include in the population.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...values) {\n return Math.sqrt(VARPA.compute(...values));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // VAR\n // -----------------------------------------------------------------------------\n const VAR = {\n description: _lt(\"Variance.\"),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range of the sample.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to include in the sample.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...values) {\n return variance(values, true, false);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // VAR.P\n // -----------------------------------------------------------------------------\n const VAR_P = {\n description: _lt(\"Variance of entire population.\"),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range of the population.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to include in the population.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...values) {\n return variance(values, false, false);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // VAR.S\n // -----------------------------------------------------------------------------\n const VAR_S = {\n description: _lt(\"Variance.\"),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range of the sample.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to include in the sample.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...values) {\n return variance(values, true, false);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // VARA\n // -----------------------------------------------------------------------------\n const VARA = {\n description: _lt(\"Variance of sample (text as 0).\"),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range of the sample.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to include in the sample.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...values) {\n return variance(values, true, true);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // VARP\n // -----------------------------------------------------------------------------\n const VARP = {\n description: _lt(\"Variance of entire population.\"),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range of the population.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to include in the population.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...values) {\n return variance(values, false, false);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // VARPA\n // -----------------------------------------------------------------------------\n const VARPA = {\n description: _lt(\"Variance of entire population (text as 0).\"),\n args: args(`\n value1 (number, range) ${_lt(\"The first value or range of the population.\")}\n value2 (number, range, repeating) ${_lt(\"Additional values or ranges to include in the population.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (...values) {\n return variance(values, false, true);\n },\n isExported: true,\n };\n\n var statistical = /*#__PURE__*/Object.freeze({\n __proto__: null,\n AVEDEV: AVEDEV,\n AVERAGE: AVERAGE,\n AVERAGE_WEIGHTED: AVERAGE_WEIGHTED,\n AVERAGEA: AVERAGEA,\n AVERAGEIF: AVERAGEIF,\n AVERAGEIFS: AVERAGEIFS,\n COUNT: COUNT,\n COUNTA: COUNTA,\n COVAR: COVAR,\n COVARIANCE_P: COVARIANCE_P,\n COVARIANCE_S: COVARIANCE_S,\n LARGE: LARGE,\n MAX: MAX,\n MAXA: MAXA,\n MAXIFS: MAXIFS,\n MEDIAN: MEDIAN,\n MIN: MIN,\n MINA: MINA,\n MINIFS: MINIFS,\n PERCENTILE: PERCENTILE,\n PERCENTILE_EXC: PERCENTILE_EXC,\n PERCENTILE_INC: PERCENTILE_INC,\n QUARTILE: QUARTILE,\n QUARTILE_EXC: QUARTILE_EXC,\n QUARTILE_INC: QUARTILE_INC,\n SMALL: SMALL,\n STDEV: STDEV,\n STDEV_P: STDEV_P,\n STDEV_S: STDEV_S,\n STDEVA: STDEVA,\n STDEVP: STDEVP,\n STDEVPA: STDEVPA,\n VAR: VAR,\n VAR_P: VAR_P,\n VAR_S: VAR_S,\n VARA: VARA,\n VARP: VARP,\n VARPA: VARPA\n });\n\n function getMatchingCells(database, field, criteria) {\n // Example\n var _a;\n // # DATABASE # CRITERIA # field = \"C\"\n //\n // | A | B | C | | A | C |\n // |===========| |=======|\n // | 1 | x | j | |<2 | j |\n // | 1 | Z | k | | | 7 |\n // | 5 | y | 7 |\n // 1 - Select coordinates of database columns ----------------------------------------------------\n const indexColNameDB = new Map();\n const dimRowDB = database.length;\n for (let indexCol = dimRowDB - 1; indexCol >= 0; indexCol--) {\n indexColNameDB.set(toString(database[indexCol][0]).toUpperCase(), indexCol);\n }\n // Example continuation: indexColNameDB = {\"A\" => 0, \"B\" => 1, \"C\" => 2}\n // 2 - Check if the field parameter exists in the column names of the database -------------------\n // field may either be a text label corresponding to a column header in the\n // first row of database or a numeric index indicating which column to consider,\n // where the first column has the value 1.\n if (typeof field !== \"number\" && typeof field !== \"string\") {\n throw new Error(_lt(\"The field must be a number or a string\"));\n }\n let index;\n if (typeof field === \"number\") {\n index = Math.trunc(field) - 1;\n if (index < 0 || dimRowDB - 1 < index) {\n throw new Error(_lt(\"The field (%s) must be one of %s or must be a number between 1 and %s inclusive.\", field.toString(), dimRowDB.toString()));\n }\n }\n else {\n const colName = toString(field).toUpperCase();\n index = (_a = indexColNameDB.get(colName)) !== null && _a !== void 0 ? _a : -1;\n if (index === -1) {\n throw new Error(_lt(\"The field (%s) must be one of %s.\", toString(field), [...indexColNameDB.keys()].toString()));\n }\n }\n // Example continuation: index = 2\n // 3 - For each criteria row, find database row that correspond ----------------------------------\n const dimColCriteria = criteria[0].length;\n if (dimColCriteria < 2) {\n throw new Error(_lt(\"The criteria range contains %s row, it must be at least 2 rows.\", dimColCriteria.toString()));\n }\n let matchingRows = new Set();\n const dimColDB = database[0].length;\n for (let indexRow = 1; indexRow < dimColCriteria; indexRow++) {\n let args = [];\n let existColNameDB = true;\n for (let indexCol = 0; indexCol < criteria.length; indexCol++) {\n const currentName = toString(criteria[indexCol][0]).toUpperCase();\n const indexColDB = indexColNameDB.get(currentName);\n const criter = criteria[indexCol][indexRow];\n if (criter !== undefined) {\n if (indexColDB !== undefined) {\n args.push([database[indexColDB].slice(1, dimColDB)]);\n args.push(criter);\n }\n else {\n existColNameDB = false;\n break;\n }\n }\n }\n // Example continuation: args1 = [[1,1,5], \"<2\", [\"j\",\"k\",7], \"j\"]\n // Example continuation: args2 = [[\"j\",\"k\",7], \"7\"]\n if (existColNameDB) {\n if (args.length > 0) {\n visitMatchingRanges(args, (i, j) => {\n matchingRows.add(j);\n }, true);\n }\n else {\n // return indices of each database row when a criteria table row is void\n matchingRows = new Set(Array(dimColDB - 1).keys());\n break;\n }\n }\n }\n // Example continuation: matchingRows = {0, 2}\n // 4 - return for each database row corresponding, the cells corresponding to the field parameter\n const fieldCol = database[index];\n // Example continuation:: fieldCol = [\"C\", \"j\", \"k\", 7]\n const matchingCells = [...matchingRows].map((x) => fieldCol[x + 1]);\n // Example continuation:: matchingCells = [\"j\", 7]\n return matchingCells;\n }\n const databaseArgs = args(`\n database (range) ${_lt(\"The array or range containing the data to consider, structured in such a way that the first row contains the labels for each column's values.\")}\n field (any) ${_lt(\"Indicates which column in database contains the values to be extracted and operated on.\")}\n criteria (range) ${_lt(\"An array or range containing zero or more criteria to filter the database values by before operating.\")}\n`);\n // -----------------------------------------------------------------------------\n // DAVERAGE\n // -----------------------------------------------------------------------------\n const DAVERAGE = {\n description: _lt(\"Average of a set of values from a table-like range.\"),\n args: databaseArgs,\n returns: [\"NUMBER\"],\n compute: function (database, field, criteria) {\n const cells = getMatchingCells(database, field, criteria);\n return AVERAGE.compute([cells]);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DCOUNT\n // -----------------------------------------------------------------------------\n const DCOUNT = {\n description: _lt(\"Counts values from a table-like range.\"),\n args: databaseArgs,\n returns: [\"NUMBER\"],\n compute: function (database, field, criteria) {\n const cells = getMatchingCells(database, field, criteria);\n return COUNT.compute([cells]);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DCOUNTA\n // -----------------------------------------------------------------------------\n const DCOUNTA = {\n description: _lt(\"Counts values and text from a table-like range.\"),\n args: databaseArgs,\n returns: [\"NUMBER\"],\n compute: function (database, field, criteria) {\n const cells = getMatchingCells(database, field, criteria);\n return COUNTA.compute([cells]);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DGET\n // -----------------------------------------------------------------------------\n const DGET = {\n description: _lt(\"Single value from a table-like range.\"),\n args: databaseArgs,\n returns: [\"NUMBER\"],\n compute: function (database, field, criteria) {\n const cells = getMatchingCells(database, field, criteria);\n assert(() => cells.length === 1, _lt(\"More than one match found in DGET evaluation.\"));\n return cells[0];\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DMAX\n // -----------------------------------------------------------------------------\n const DMAX = {\n description: _lt(\"Maximum of values from a table-like range.\"),\n args: databaseArgs,\n returns: [\"NUMBER\"],\n compute: function (database, field, criteria) {\n const cells = getMatchingCells(database, field, criteria);\n return MAX.compute([cells]);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DMIN\n // -----------------------------------------------------------------------------\n const DMIN = {\n description: _lt(\"Minimum of values from a table-like range.\"),\n args: databaseArgs,\n returns: [\"NUMBER\"],\n compute: function (database, field, criteria) {\n const cells = getMatchingCells(database, field, criteria);\n return MIN.compute([cells]);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DPRODUCT\n // -----------------------------------------------------------------------------\n const DPRODUCT = {\n description: _lt(\"Product of values from a table-like range.\"),\n args: databaseArgs,\n returns: [\"NUMBER\"],\n compute: function (database, field, criteria) {\n const cells = getMatchingCells(database, field, criteria);\n return PRODUCT.compute([cells]);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DSTDEV\n // -----------------------------------------------------------------------------\n const DSTDEV = {\n description: _lt(\"Standard deviation of population sample from table.\"),\n args: databaseArgs,\n returns: [\"NUMBER\"],\n compute: function (database, field, criteria) {\n const cells = getMatchingCells(database, field, criteria);\n return STDEV.compute([cells]);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DSTDEVP\n // -----------------------------------------------------------------------------\n const DSTDEVP = {\n description: _lt(\"Standard deviation of entire population from table.\"),\n args: databaseArgs,\n returns: [\"NUMBER\"],\n compute: function (database, field, criteria) {\n const cells = getMatchingCells(database, field, criteria);\n return STDEVP.compute([cells]);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DSUM\n // -----------------------------------------------------------------------------\n const DSUM = {\n description: _lt(\"Sum of values from a table-like range.\"),\n args: databaseArgs,\n returns: [\"NUMBER\"],\n compute: function (database, field, criteria) {\n const cells = getMatchingCells(database, field, criteria);\n return SUM.compute([cells]);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DVAR\n // -----------------------------------------------------------------------------\n const DVAR = {\n description: _lt(\"Variance of population sample from table-like range.\"),\n args: databaseArgs,\n returns: [\"NUMBER\"],\n compute: function (database, field, criteria) {\n const cells = getMatchingCells(database, field, criteria);\n return VAR.compute([cells]);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DVARP\n // -----------------------------------------------------------------------------\n const DVARP = {\n description: _lt(\"Variance of a population from a table-like range.\"),\n args: databaseArgs,\n returns: [\"NUMBER\"],\n compute: function (database, field, criteria) {\n const cells = getMatchingCells(database, field, criteria);\n return VARP.compute([cells]);\n },\n isExported: true,\n };\n\n var database = /*#__PURE__*/Object.freeze({\n __proto__: null,\n DAVERAGE: DAVERAGE,\n DCOUNT: DCOUNT,\n DCOUNTA: DCOUNTA,\n DGET: DGET,\n DMAX: DMAX,\n DMIN: DMIN,\n DPRODUCT: DPRODUCT,\n DSTDEV: DSTDEV,\n DSTDEVP: DSTDEVP,\n DSUM: DSUM,\n DVAR: DVAR,\n DVARP: DVARP\n });\n\n const DEFAULT_TYPE = 1;\n const DEFAULT_WEEKEND = 1;\n // -----------------------------------------------------------------------------\n // DATE\n // -----------------------------------------------------------------------------\n const DATE = {\n description: _lt(\"Converts year/month/day into a date.\"),\n args: args(`\n year (number) ${_lt(\"The year component of the date.\")}\n month (number) ${_lt(\"The month component of the date.\")}\n day (number) ${_lt(\"The day component of the date.\")}\n `),\n returns: [\"DATE\"],\n computeFormat: () => \"m/d/yyyy\",\n compute: function (year, month, day) {\n let _year = Math.trunc(toNumber(year));\n const _month = Math.trunc(toNumber(month));\n const _day = Math.trunc(toNumber(day));\n // For years less than 0 or greater than 10000, return #ERROR.\n assert(() => 0 <= _year && _year <= 9999, _lt(\"The year (%s) must be between 0 and 9999 inclusive.\", _year.toString()));\n // Between 0 and 1899, we add that value to 1900 to calculate the year\n if (_year < 1900) {\n _year += 1900;\n }\n const jsDate = new Date(_year, _month - 1, _day);\n const result = jsDateToRoundNumber(jsDate);\n assert(() => result >= 0, _lt(`The function [[FUNCTION_NAME]] result must be greater than or equal 01/01/1900.`));\n return result;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DATEVALUE\n // -----------------------------------------------------------------------------\n const DATEVALUE = {\n description: _lt(\"Converts a date string to a date value.\"),\n args: args(`\n date_string (string) ${_lt(\"The string representing the date.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (dateString) {\n const _dateString = toString(dateString);\n const internalDate = parseDateTime(_dateString);\n assert(() => internalDate !== null, _lt(\"The date_string (%s) cannot be parsed to date/time.\", _dateString.toString()));\n return Math.trunc(internalDate.value);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DAY\n // -----------------------------------------------------------------------------\n const DAY = {\n description: _lt(\"Day of the month that a specific date falls on.\"),\n args: args(`\n date (string) ${_lt(\"The date from which to extract the day.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (date) {\n return toJsDate(date).getDate();\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DAYS\n // -----------------------------------------------------------------------------\n const DAYS = {\n description: _lt(\"Number of days between two dates.\"),\n args: args(`\n end_date (date) ${_lt(\"The end of the date range.\")}\n start_date (date) ${_lt(\"The start of the date range.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (endDate, startDate) {\n const _endDate = toJsDate(endDate);\n const _startDate = toJsDate(startDate);\n const dateDif = _endDate.getTime() - _startDate.getTime();\n return Math.round(dateDif / MS_PER_DAY);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DAYS360\n // -----------------------------------------------------------------------------\n const DEFAULT_DAY_COUNT_METHOD = 0;\n const DAYS360 = {\n description: _lt(\"Number of days between two dates on a 360-day year (months of 30 days).\"),\n args: args(`\n start_date (date) ${_lt(\"The start date to consider in the calculation.\")}\n end_date (date) ${_lt(\"The end date to consider in the calculation.\")}\n method (number, default=${DEFAULT_DAY_COUNT_METHOD}) ${_lt(\"An indicator of what day count method to use. (0) US NASD method (1) European method\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (startDate, endDate, method = DEFAULT_DAY_COUNT_METHOD) {\n const _startDate = toNumber(startDate);\n const _endDate = toNumber(endDate);\n const dayCountConvention = toBoolean(method) ? 4 : 0;\n const yearFrac = YEARFRAC.compute(startDate, endDate, dayCountConvention);\n return Math.sign(_endDate - _startDate) * Math.round(yearFrac * 360);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // EDATE\n // -----------------------------------------------------------------------------\n const EDATE = {\n description: _lt(\"Date a number of months before/after another date.\"),\n args: args(`\n start_date (date) ${_lt(\"The date from which to calculate the result.\")}\n months (number) ${_lt(\"The number of months before (negative) or after (positive) 'start_date' to calculate.\")}\n `),\n returns: [\"DATE\"],\n computeFormat: () => \"m/d/yyyy\",\n compute: function (startDate, months) {\n const _startDate = toJsDate(startDate);\n const _months = Math.trunc(toNumber(months));\n const jsDate = addMonthsToDate(_startDate, _months, false);\n return jsDateToRoundNumber(jsDate);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // EOMONTH\n // -----------------------------------------------------------------------------\n const EOMONTH = {\n description: _lt(\"Last day of a month before or after a date.\"),\n args: args(`\n start_date (date) ${_lt(\"The date from which to calculate the result.\")}\n months (number) ${_lt(\"The number of months before (negative) or after (positive) 'start_date' to consider.\")}\n `),\n returns: [\"DATE\"],\n computeFormat: () => \"m/d/yyyy\",\n compute: function (startDate, months) {\n const _startDate = toJsDate(startDate);\n const _months = Math.trunc(toNumber(months));\n const yStart = _startDate.getFullYear();\n const mStart = _startDate.getMonth();\n const jsDate = new Date(yStart, mStart + _months + 1, 0);\n return jsDateToRoundNumber(jsDate);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // HOUR\n // -----------------------------------------------------------------------------\n const HOUR = {\n description: _lt(\"Hour component of a specific time.\"),\n args: args(`\n time (date) ${_lt(\"The time from which to calculate the hour component.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (date) {\n return toJsDate(date).getHours();\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ISOWEEKNUM\n // -----------------------------------------------------------------------------\n const ISOWEEKNUM = {\n description: _lt(\"ISO week number of the year.\"),\n args: args(`\n date (date) ${_lt(\"The date for which to determine the ISO week number. Must be a reference to a cell containing a date, a function returning a date type, or a number.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (date) {\n const _date = toJsDate(date);\n const y = _date.getFullYear();\n // 1 - As the 1st week of a year can start the previous year or after the 1st\n // january we first look if the date is in the weeks of the current year, previous\n // year or year after.\n // A - We look for the current year, the first days of the first week\n // and the last days of the last week\n // The first week of the year is the week that contains the first\n // Thursday of the year.\n let firstThursday = 1;\n while (new Date(y, 0, firstThursday).getDay() !== 4) {\n firstThursday += 1;\n }\n const firstDayOfFirstWeek = new Date(y, 0, firstThursday - 3);\n // The last week of the year is the week that contains the last Thursday of\n // the year.\n let lastThursday = 31;\n while (new Date(y, 11, lastThursday).getDay() !== 4) {\n lastThursday -= 1;\n }\n const lastDayOfLastWeek = new Date(y, 11, lastThursday + 3);\n // B - If our date > lastDayOfLastWeek then it's in the weeks of the year after\n // If our date < firstDayOfFirstWeek then it's in the weeks of the year before\n let offsetYear;\n if (firstDayOfFirstWeek.getTime() <= _date.getTime()) {\n if (_date.getTime() <= lastDayOfLastWeek.getTime()) {\n offsetYear = 0;\n }\n else {\n offsetYear = 1;\n }\n }\n else {\n offsetYear = -1;\n }\n // 2 - now that the year is known, we are looking at the difference between\n // the first day of this year and the date. The difference in days divided by\n // 7 gives us the week number\n let firstDay;\n switch (offsetYear) {\n case 0:\n firstDay = firstDayOfFirstWeek;\n break;\n case 1:\n // firstDay is the 1st day of the 1st week of the year after\n // firstDay = lastDayOfLastWeek + 1 Day\n firstDay = new Date(y, 11, lastThursday + 3 + 1);\n break;\n case -1:\n // firstDay is the 1st day of the 1st week of the previous year.\n // The first week of the previous year is the week that contains the\n // first Thursday of the previous year.\n let firstThursdayPreviousYear = 1;\n while (new Date(y - 1, 0, firstThursdayPreviousYear).getDay() !== 4) {\n firstThursdayPreviousYear += 1;\n }\n firstDay = new Date(y - 1, 0, firstThursdayPreviousYear - 3);\n break;\n }\n const diff = (_date.getTime() - firstDay.getTime()) / MS_PER_DAY;\n return Math.floor(diff / 7) + 1;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // MINUTE\n // -----------------------------------------------------------------------------\n const MINUTE = {\n description: _lt(\"Minute component of a specific time.\"),\n args: args(`\n time (date) ${_lt(\"The time from which to calculate the minute component.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (date) {\n return toJsDate(date).getMinutes();\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // MONTH\n // -----------------------------------------------------------------------------\n const MONTH = {\n description: _lt(\"Month of the year a specific date falls in\"),\n args: args(`\n date (date) ${_lt(\"The date from which to extract the month.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (date) {\n return toJsDate(date).getMonth() + 1;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // NETWORKDAYS\n // -----------------------------------------------------------------------------\n const NETWORKDAYS = {\n description: _lt(\"Net working days between two provided days.\"),\n args: args(`\n start_date (date) ${_lt(\"The start date of the period from which to calculate the number of net working days.\")}\n end_date (date) ${_lt(\"The end date of the period from which to calculate the number of net working days.\")}\n holidays (date, range, optional) ${_lt(\"A range or array constant containing the date serial numbers to consider holidays.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (startDate, endDate, holidays) {\n return NETWORKDAYS_INTL.compute(startDate, endDate, 1, holidays);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // NETWORKDAYS.INTL\n // -----------------------------------------------------------------------------\n /**\n * Transform weekend Spreadsheet information into Date Day JavaScript information.\n * Take string (String method) or number (Number method), return array of numbers.\n *\n * String method: weekends can be specified using seven 0\u2019s and 1\u2019s, where the\n * first number in the set represents Monday and the last number is for Sunday.\n * A zero means that the day is a work day, a 1 means that the day is a weekend.\n * For example, \u201c0000011\u201d would mean Saturday and Sunday are weekends.\n *\n * Number method: instead of using the string method above, a single number can\n * be used. 1 = Saturday/Sunday are weekends, 2 = Sunday/Monday, and this pattern\n * repeats until 7 = Friday/Saturday. 11 = Sunday is the only weekend, 12 = Monday\n * is the only weekend, and this pattern repeats until 17 = Saturday is the only\n * weekend.\n *\n * Example:\n * - 11 return [0] (correspond to Sunday)\n * - 12 return [1] (correspond to Monday)\n * - 3 return [1,2] (correspond to Monday and Tuesday)\n * - \"0101010\" return [2,4,6] (correspond to Tuesday, Thursday and Saturday)\n */\n function weekendToDayNumber(weekend) {\n // case \"string\"\n if (typeof weekend === \"string\") {\n assert(() => {\n if (weekend.length !== 7) {\n return false;\n }\n for (let day of weekend) {\n if (day !== \"0\" && day !== \"1\") {\n return false;\n }\n }\n return true;\n }, _lt('When weekend is a string (%s) it must be composed of \"0\" or \"1\".', weekend));\n let result = [];\n for (let i = 0; i < 7; i++) {\n if (weekend[i] === \"1\") {\n result.push((i + 1) % 7);\n }\n }\n return result;\n }\n //case \"number\"\n if (typeof weekend === \"number\") {\n assert(() => (1 <= weekend && weekend <= 7) || (11 <= weekend && weekend <= 17), _lt(\"The weekend (%s) must be a string or a number in the range 1-7 or 11-17.\", weekend.toString()));\n // case 1 <= weekend <= 7\n if (weekend <= 7) {\n // 1 = Saturday/Sunday are weekends\n // 2 = Sunday/Monday\n // ...\n // 7 = Friday/Saturday.\n return [weekend - 2 === -1 ? 6 : weekend - 2, weekend - 1];\n }\n // case 11 <= weekend <= 17\n // 11 = Sunday is the only weekend\n // 12 = Monday is the only weekend\n // ...\n // 17 = Saturday is the only weekend.\n return [weekend - 11];\n }\n throw Error(_lt(\"The weekend must be a number or a string.\"));\n }\n const NETWORKDAYS_INTL = {\n description: _lt(\"Net working days between two dates (specifying weekends).\"),\n args: args(`\n start_date (date) ${_lt(\"The start date of the period from which to calculate the number of net working days.\")}\n end_date (date) ${_lt(\"The end date of the period from which to calculate the number of net working days.\")}\n weekend (any, default=${DEFAULT_WEEKEND}) ${_lt(\"A number or string representing which days of the week are considered weekends.\")}\n holidays (date, range, optional) ${_lt(\"A range or array constant containing the dates to consider as holidays.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (startDate, endDate, weekend = DEFAULT_WEEKEND, holidays) {\n const _startDate = toJsDate(startDate);\n const _endDate = toJsDate(endDate);\n const daysWeekend = weekendToDayNumber(weekend);\n let timesHoliday = new Set();\n if (holidays !== undefined) {\n visitAny([holidays], (h) => {\n const holiday = toJsDate(h);\n timesHoliday.add(holiday.getTime());\n });\n }\n const invertDate = _startDate.getTime() > _endDate.getTime();\n const stopDate = new Date((invertDate ? _startDate : _endDate).getTime());\n let stepDate = new Date((invertDate ? _endDate : _startDate).getTime());\n const timeStopDate = stopDate.getTime();\n let timeStepDate = stepDate.getTime();\n let netWorkingDay = 0;\n while (timeStepDate <= timeStopDate) {\n if (!daysWeekend.includes(stepDate.getDay()) && !timesHoliday.has(timeStepDate)) {\n netWorkingDay += 1;\n }\n stepDate.setDate(stepDate.getDate() + 1);\n timeStepDate = stepDate.getTime();\n }\n return invertDate ? -netWorkingDay : netWorkingDay;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // NOW\n // -----------------------------------------------------------------------------\n const NOW = {\n description: _lt(\"Current date and time as a date value.\"),\n args: [],\n returns: [\"DATE\"],\n computeFormat: () => \"m/d/yyyy hh:mm:ss\",\n compute: function () {\n let today = new Date();\n today.setMilliseconds(0);\n const delta = today.getTime() - INITIAL_1900_DAY.getTime();\n const time = today.getHours() / 24 + today.getMinutes() / 1440 + today.getSeconds() / 86400;\n return Math.floor(delta / MS_PER_DAY) + time;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // SECOND\n // -----------------------------------------------------------------------------\n const SECOND = {\n description: _lt(\"Minute component of a specific time.\"),\n args: args(`\n time (date) ${_lt(\"The time from which to calculate the second component.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (date) {\n return toJsDate(date).getSeconds();\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // TIME\n // -----------------------------------------------------------------------------\n const TIME = {\n description: _lt(\"Converts hour/minute/second into a time.\"),\n args: args(`\n hour (number) ${_lt(\"The hour component of the time.\")}\n minute (number) ${_lt(\"The minute component of the time.\")}\n second (number) ${_lt(\"The second component of the time.\")}\n `),\n returns: [\"DATE\"],\n computeFormat: () => \"hh:mm:ss a\",\n compute: function (hour, minute, second) {\n let _hour = Math.trunc(toNumber(hour));\n let _minute = Math.trunc(toNumber(minute));\n let _second = Math.trunc(toNumber(second));\n _minute += Math.floor(_second / 60);\n _second = (_second % 60) + (_second < 0 ? 60 : 0);\n _hour += Math.floor(_minute / 60);\n _minute = (_minute % 60) + (_minute < 0 ? 60 : 0);\n _hour %= 24;\n assert(() => _hour >= 0, _lt(`The function [[FUNCTION_NAME]] result cannot be negative`));\n return _hour / 24 + _minute / (24 * 60) + _second / (24 * 60 * 60);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // TIMEVALUE\n // -----------------------------------------------------------------------------\n const TIMEVALUE = {\n description: _lt(\"Converts a time string into its serial number representation.\"),\n args: args(`\n time_string (string) ${_lt(\"The string that holds the time representation.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (timeString) {\n const _timeString = toString(timeString);\n const internalDate = parseDateTime(_timeString);\n assert(() => internalDate !== null, _lt(\"The time_string (%s) cannot be parsed to date/time.\", _timeString));\n const result = internalDate.value - Math.trunc(internalDate.value);\n return result < 0 ? 1 + result : result;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // TODAY\n // -----------------------------------------------------------------------------\n const TODAY = {\n description: _lt(\"Current date as a date value.\"),\n args: [],\n returns: [\"DATE\"],\n computeFormat: () => \"m/d/yyyy\",\n compute: function () {\n const today = new Date();\n const jsDate = new Date(today.getFullYear(), today.getMonth(), today.getDate());\n return jsDateToRoundNumber(jsDate);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // WEEKDAY\n // -----------------------------------------------------------------------------\n const WEEKDAY = {\n description: _lt(\"Day of the week of the date provided (as number).\"),\n args: args(`\n date (date) ${_lt(\"The date for which to determine the day of the week. Must be a reference to a cell containing a date, a function returning a date type, or a number.\")}\n type (number, default=${DEFAULT_TYPE}) ${_lt(\"A number indicating which numbering system to use to represent weekdays. By default, counts starting with Sunday = 1.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (date, type = DEFAULT_TYPE) {\n const _date = toJsDate(date);\n const _type = Math.round(toNumber(type));\n const m = _date.getDay();\n assert(() => [1, 2, 3].includes(_type), _lt(\"The type (%s) must be 1, 2 or 3.\", _type.toString()));\n if (_type === 1)\n return m + 1;\n if (_type === 2)\n return m === 0 ? 7 : m;\n return m === 0 ? 6 : m - 1;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // WEEKNUM\n // -----------------------------------------------------------------------------\n const WEEKNUM = {\n description: _lt(\"Week number of the year.\"),\n args: args(`\n date (date) ${_lt(\"The date for which to determine the week number. Must be a reference to a cell containing a date, a function returning a date type, or a number.\")}\n type (number, default=${DEFAULT_TYPE}) ${_lt(\"A number representing the day that a week starts on. Sunday = 1.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (date, type = DEFAULT_TYPE) {\n const _date = toJsDate(date);\n const _type = Math.round(toNumber(type));\n assert(() => _type === 1 || _type === 2 || (11 <= _type && _type <= 17) || _type === 21, _lt(\"The type (%s) is out of range.\", _type.toString()));\n if (_type === 21) {\n return ISOWEEKNUM.compute(date);\n }\n let startDayOfWeek;\n if (_type === 1 || _type === 2) {\n startDayOfWeek = _type - 1;\n }\n else {\n // case 11 <= _type <= 17\n startDayOfWeek = _type - 10 === 7 ? 0 : _type - 10;\n }\n const y = _date.getFullYear();\n let dayStart = 1;\n let startDayOfFirstWeek = new Date(y, 0, dayStart);\n while (startDayOfFirstWeek.getDay() !== startDayOfWeek) {\n dayStart += 1;\n startDayOfFirstWeek = new Date(y, 0, dayStart);\n }\n const dif = (_date.getTime() - startDayOfFirstWeek.getTime()) / MS_PER_DAY;\n if (dif < 0) {\n return 1;\n }\n return Math.floor(dif / 7) + (dayStart === 1 ? 1 : 2);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // WORKDAY\n // -----------------------------------------------------------------------------\n const WORKDAY = {\n description: _lt(\"Date after a number of workdays.\"),\n args: args(`\n start_date (date) ${_lt(\"The date from which to begin counting.\")}\n num_days (number) ${_lt(\"The number of working days to advance from start_date. If negative, counts backwards.\")}\n holidays (date, range, optional) ${_lt(\"A range or array constant containing the dates to consider holidays.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: () => \"m/d/yyyy\",\n compute: function (startDate, numDays, holidays = undefined) {\n return WORKDAY_INTL.compute(startDate, numDays, 1, holidays);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // WORKDAY.INTL\n // -----------------------------------------------------------------------------\n const WORKDAY_INTL = {\n description: _lt(\"Date after a number of workdays (specifying weekends).\"),\n args: args(`\n start_date (date) ${_lt(\"The date from which to begin counting.\")}\n num_days (number) ${_lt(\"The number of working days to advance from start_date. If negative, counts backwards.\")}\n weekend (any, default=${DEFAULT_WEEKEND}) ${_lt(\"A number or string representing which days of the week are considered weekends.\")}\n holidays (date, range, optional) ${_lt(\"A range or array constant containing the dates to consider holidays.\")}\n `),\n returns: [\"DATE\"],\n computeFormat: () => \"m/d/yyyy\",\n compute: function (startDate, numDays, weekend = DEFAULT_WEEKEND, holidays) {\n let _startDate = toJsDate(startDate);\n let _numDays = Math.trunc(toNumber(numDays));\n if (typeof weekend === \"string\") {\n assert(() => weekend !== \"1111111\", _lt(\"The weekend (%s) must be different from '1111111'.\", weekend));\n }\n const daysWeekend = weekendToDayNumber(weekend);\n let timesHoliday = new Set();\n if (holidays !== undefined) {\n visitAny([holidays], (h) => {\n const holiday = toJsDate(h);\n timesHoliday.add(holiday.getTime());\n });\n }\n let stepDate = new Date(_startDate.getTime());\n let timeStepDate = stepDate.getTime();\n const unitDay = Math.sign(_numDays);\n let stepDay = Math.abs(_numDays);\n while (stepDay > 0) {\n stepDate.setDate(stepDate.getDate() + unitDay);\n timeStepDate = stepDate.getTime();\n if (!daysWeekend.includes(stepDate.getDay()) && !timesHoliday.has(timeStepDate)) {\n stepDay -= 1;\n }\n }\n const delta = timeStepDate - INITIAL_1900_DAY.getTime();\n return Math.round(delta / MS_PER_DAY);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // YEAR\n // -----------------------------------------------------------------------------\n const YEAR = {\n description: _lt(\"Year specified by a given date.\"),\n args: args(`\n date (date) ${_lt(\"The date from which to extract the year.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (date) {\n return toJsDate(date).getFullYear();\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // YEARFRAC\n // -----------------------------------------------------------------------------\n const DEFAULT_DAY_COUNT_CONVENTION$1 = 0;\n const YEARFRAC = {\n description: _lt(\"Exact number of years between two dates.\"),\n args: args(`\n start_date (date) ${_lt(\"The start date to consider in the calculation. Must be a reference to a cell containing a date, a function returning a date type, or a number.\")}\n end_date (date) ${_lt(\"The end date to consider in the calculation. Must be a reference to a cell containing a date, a function returning a date type, or a number.\")}\n day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION$1}) ${_lt(\"An indicator of what day count method to use.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (startDate, endDate, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION$1) {\n let _startDate = Math.trunc(toNumber(startDate));\n let _endDate = Math.trunc(toNumber(endDate));\n const _dayCountConvention = Math.trunc(toNumber(dayCountConvention));\n assert(() => _startDate >= 0, _lt(\"The start_date (%s) must be positive or null.\", _startDate.toString()));\n assert(() => _endDate >= 0, _lt(\"The end_date (%s) must be positive or null.\", _endDate.toString()));\n assert(() => 0 <= _dayCountConvention && _dayCountConvention <= 4, _lt(\"The day_count_convention (%s) must be between 0 and 4 inclusive.\", _dayCountConvention.toString()));\n return getYearFrac(_startDate, _endDate, _dayCountConvention);\n },\n };\n // -----------------------------------------------------------------------------\n // MONTH.START\n // -----------------------------------------------------------------------------\n const MONTH_START = {\n description: _lt(\"First day of the month preceding a date.\"),\n args: args(`\n date (date) ${_lt(\"The date from which to calculate the result.\")}\n `),\n returns: [\"DATE\"],\n computeFormat: () => \"m/d/yyyy\",\n compute: function (date) {\n const _startDate = toJsDate(date);\n const yStart = _startDate.getFullYear();\n const mStart = _startDate.getMonth();\n const jsDate = new Date(yStart, mStart, 1);\n return jsDateToRoundNumber(jsDate);\n },\n };\n // -----------------------------------------------------------------------------\n // MONTH.END\n // -----------------------------------------------------------------------------\n const MONTH_END = {\n description: _lt(\"Last day of the month following a date.\"),\n args: args(`\n date (date) ${_lt(\"The date from which to calculate the result.\")}\n `),\n returns: [\"DATE\"],\n computeFormat: () => \"m/d/yyyy\",\n compute: function (date) {\n return EOMONTH.compute(date, 0);\n },\n };\n // -----------------------------------------------------------------------------\n // QUARTER\n // -----------------------------------------------------------------------------\n const QUARTER = {\n description: _lt(\"Quarter of the year a specific date falls in\"),\n args: args(`\n date (date) ${_lt(\"The date from which to extract the quarter.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (date) {\n return Math.ceil((toJsDate(date).getMonth() + 1) / 3);\n },\n };\n // -----------------------------------------------------------------------------\n // QUARTER.START\n // -----------------------------------------------------------------------------\n const QUARTER_START = {\n description: _lt(\"First day of the quarter of the year a specific date falls in.\"),\n args: args(`\n date (date) ${_lt(\"The date from which to calculate the start of quarter.\")}\n `),\n returns: [\"DATE\"],\n computeFormat: () => \"m/d/yyyy\",\n compute: function (date) {\n const quarter = QUARTER.compute(date);\n const year = YEAR.compute(date);\n const jsDate = new Date(year, (quarter - 1) * 3, 1);\n return jsDateToRoundNumber(jsDate);\n },\n };\n // -----------------------------------------------------------------------------\n // QUARTER.END\n // -----------------------------------------------------------------------------\n const QUARTER_END = {\n description: _lt(\"Last day of the quarter of the year a specific date falls in.\"),\n args: args(`\n date (date) ${_lt(\"The date from which to calculate the end of quarter.\")}\n `),\n returns: [\"DATE\"],\n computeFormat: () => \"m/d/yyyy\",\n compute: function (date) {\n const quarter = QUARTER.compute(date);\n const year = YEAR.compute(date);\n const jsDate = new Date(year, quarter * 3, 0);\n return jsDateToRoundNumber(jsDate);\n },\n };\n // -----------------------------------------------------------------------------\n // YEAR.START\n // -----------------------------------------------------------------------------\n const YEAR_START = {\n description: _lt(\"First day of the year a specific date falls in.\"),\n args: args(`\n date (date) ${_lt(\"The date from which to calculate the start of the year.\")}\n `),\n returns: [\"DATE\"],\n computeFormat: () => \"m/d/yyyy\",\n compute: function (date) {\n const year = YEAR.compute(date);\n const jsDate = new Date(year, 0, 1);\n return jsDateToRoundNumber(jsDate);\n },\n };\n // -----------------------------------------------------------------------------\n // YEAR.END\n // -----------------------------------------------------------------------------\n const YEAR_END = {\n description: _lt(\"Last day of the year a specific date falls in.\"),\n args: args(`\n date (date) ${_lt(\"The date from which to calculate the end of the year.\")}\n `),\n returns: [\"DATE\"],\n computeFormat: () => \"m/d/yyyy\",\n compute: function (date) {\n const year = YEAR.compute(date);\n const jsDate = new Date(year + 1, 0, 0);\n return jsDateToRoundNumber(jsDate);\n },\n };\n\n var date = /*#__PURE__*/Object.freeze({\n __proto__: null,\n DATE: DATE,\n DATEVALUE: DATEVALUE,\n DAY: DAY,\n DAYS: DAYS,\n DAYS360: DAYS360,\n EDATE: EDATE,\n EOMONTH: EOMONTH,\n HOUR: HOUR,\n ISOWEEKNUM: ISOWEEKNUM,\n MINUTE: MINUTE,\n MONTH: MONTH,\n NETWORKDAYS: NETWORKDAYS,\n NETWORKDAYS_INTL: NETWORKDAYS_INTL,\n NOW: NOW,\n SECOND: SECOND,\n TIME: TIME,\n TIMEVALUE: TIMEVALUE,\n TODAY: TODAY,\n WEEKDAY: WEEKDAY,\n WEEKNUM: WEEKNUM,\n WORKDAY: WORKDAY,\n WORKDAY_INTL: WORKDAY_INTL,\n YEAR: YEAR,\n YEARFRAC: YEARFRAC,\n MONTH_START: MONTH_START,\n MONTH_END: MONTH_END,\n QUARTER: QUARTER,\n QUARTER_START: QUARTER_START,\n QUARTER_END: QUARTER_END,\n YEAR_START: YEAR_START,\n YEAR_END: YEAR_END\n });\n\n const DEFAULT_DELTA_ARG = 0;\n // -----------------------------------------------------------------------------\n // DELTA\n // -----------------------------------------------------------------------------\n const DELTA = {\n description: _lt(\"Compare two numeric values, returning 1 if they're equal.\"),\n args: args(`\n number1 (number) ${_lt(\"The first number to compare.\")}\n number2 (number, default=${DEFAULT_DELTA_ARG}) ${_lt(\"The second number to compare.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (number1, number2 = DEFAULT_DELTA_ARG) {\n const _number1 = toNumber(number1);\n const _number2 = toNumber(number2);\n return _number1 === _number2 ? 1 : 0;\n },\n isExported: true,\n };\n\n var engineering = /*#__PURE__*/Object.freeze({\n __proto__: null,\n DELTA: DELTA\n });\n\n /** Assert maturity date > settlement date */\n function assertMaturityAndSettlementDatesAreValid(settlement, maturity) {\n assert(() => settlement < maturity, _lt(\"The maturity (%s) must be strictly greater than the settlement (%s).\", maturity.toString(), settlement.toString()));\n }\n /** Assert settlement date > issue date */\n function assertSettlementAndIssueDatesAreValid(settlement, issue) {\n assert(() => issue < settlement, _lt(\"The settlement date (%s) must be strictly greater than the issue date (%s).\", settlement.toString(), issue.toString()));\n }\n /** Assert coupon frequency is in [1, 2, 4] */\n function assertCouponFrequencyIsValid(frequency) {\n assert(() => [1, 2, 4].includes(frequency), _lt(\"The frequency (%s) must be one of %s\", frequency.toString(), [1, 2, 4].toString()));\n }\n /** Assert dayCountConvention is between 0 and 4 */\n function assertDayCountConventionIsValid(dayCountConvention) {\n assert(() => 0 <= dayCountConvention && dayCountConvention <= 4, _lt(\"The day_count_convention (%s) must be between 0 and 4 inclusive.\", dayCountConvention.toString()));\n }\n function assertRedemptionStrictlyPositive(redemption) {\n assert(() => redemption > 0, _lt(\"The redemption (%s) must be strictly positive.\", redemption.toString()));\n }\n function assertPriceStrictlyPositive(price) {\n assert(() => price > 0, _lt(\"The price (%s) must be strictly positive.\", price.toString()));\n }\n function assertNumberOfPeriodsStrictlyPositive(nPeriods) {\n assert(() => nPeriods > 0, _lt(\"The number_of_periods (%s) must be greater than 0.\", nPeriods.toString()));\n }\n function assertRateStrictlyPositive(rate) {\n assert(() => rate > 0, _lt(\"The rate (%s) must be strictly positive.\", rate.toString()));\n }\n function assertLifeStrictlyPositive(life) {\n assert(() => life > 0, _lt(\"The life (%s) must be strictly positive.\", life.toString()));\n }\n function assertCostStrictlyPositive(cost) {\n assert(() => cost > 0, _lt(\"The cost (%s) must be strictly positive.\", cost.toString()));\n }\n function assertCostPositiveOrZero(cost) {\n assert(() => cost >= 0, _lt(\"The cost (%s) must be positive or null.\", cost.toString()));\n }\n function assertPeriodStrictlyPositive(period) {\n assert(() => period > 0, _lt(\"The period (%s) must be strictly positive.\", period.toString()));\n }\n function assertPeriodPositiveOrZero(period) {\n assert(() => period >= 0, _lt(\"The period (%s) must be positive or null.\", period.toString()));\n }\n function assertSalvagePositiveOrZero(salvage) {\n assert(() => salvage >= 0, _lt(\"The salvage (%s) must be positive or null.\", salvage.toString()));\n }\n function assertSalvageSmallerOrEqualThanCost(salvage, cost) {\n assert(() => salvage <= cost, _lt(\"The salvage (%s) must be smaller or equal than the cost (%s).\", salvage.toString(), cost.toString()));\n }\n function assertPresentValueStrictlyPositive(pv) {\n assert(() => pv > 0, _lt(\"The present value (%s) must be strictly positive.\", pv.toString()));\n }\n function assertPeriodSmallerOrEqualToLife(period, life) {\n assert(() => period <= life, _lt(\"The period (%s) must be less than or equal life (%s).\", period.toString(), life.toString()));\n }\n function assertInvestmentStrictlyPositive(investment) {\n assert(() => investment > 0, _lt(\"The investment (%s) must be strictly positive.\", investment.toString()));\n }\n function assertDiscountStrictlyPositive(discount) {\n assert(() => discount > 0, _lt(\"The discount (%s) must be strictly positive.\", discount.toString()));\n }\n function assertDiscountStrictlySmallerThanOne(discount) {\n assert(() => discount < 1, _lt(\"The discount (%s) must be smaller than 1.\", discount.toString()));\n }\n function assertDeprecationFactorStrictlyPositive(factor) {\n assert(() => factor > 0, _lt(\"The depreciation factor (%s) must be strictly positive.\", factor.toString()));\n }\n function assertSettlementLessThanOneYearBeforeMaturity(settlement, maturity) {\n const startDate = toJsDate(settlement);\n const endDate = toJsDate(maturity);\n const startDatePlusOneYear = new Date(startDate);\n startDatePlusOneYear.setFullYear(startDate.getFullYear() + 1);\n assert(() => endDate.getTime() <= startDatePlusOneYear.getTime(), _lt(\"The settlement date (%s) must at most one year after the maturity date (%s).\", settlement.toString(), maturity.toString()));\n }\n /**\n * Check if the given periods are valid. This will assert :\n *\n * - 0 < numberOfPeriods\n * - 0 < firstPeriod <= lastPeriod\n * - 0 < lastPeriod <= numberOfPeriods\n *\n */\n function assertFirstAndLastPeriodsAreValid(firstPeriod, lastPeriod, numberOfPeriods) {\n assertNumberOfPeriodsStrictlyPositive(numberOfPeriods);\n assert(() => firstPeriod > 0, _lt(\"The first_period (%s) must be strictly positive.\", firstPeriod.toString()));\n assert(() => lastPeriod > 0, _lt(\"The last_period (%s) must be strictly positive.\", lastPeriod.toString()));\n assert(() => firstPeriod <= lastPeriod, _lt(\"The first_period (%s) must be smaller or equal to the last_period (%s).\", firstPeriod.toString(), lastPeriod.toString()));\n assert(() => lastPeriod <= numberOfPeriods, _lt(\"The last_period (%s) must be smaller or equal to the number_of_periods (%s).\", firstPeriod.toString(), numberOfPeriods.toString()));\n }\n /**\n * Check if the given periods are valid. This will assert :\n *\n * - 0 < life\n * - 0 <= startPeriod <= endPeriod\n * - 0 <= endPeriod <= life\n *\n */\n function assertStartAndEndPeriodAreValid(startPeriod, endPeriod, life) {\n assertLifeStrictlyPositive(life);\n assert(() => startPeriod >= 0, _lt(\"The start_period (%s) must be greater or equal than 0.\", startPeriod.toString()));\n assert(() => endPeriod >= 0, _lt(\"The end_period (%s) must be greater or equal than 0.\", endPeriod.toString()));\n assert(() => startPeriod <= endPeriod, _lt(\"The start_period (%s) must be smaller or equal to the end_period (%s).\", startPeriod.toString(), endPeriod.toString()));\n assert(() => endPeriod <= life, _lt(\"The end_period (%s) must be smaller or equal to the life (%s).\", startPeriod.toString(), life.toString()));\n }\n function assertRateGuessStrictlyGreaterThanMinusOne(guess) {\n assert(() => guess > -1, _lt(\"The rate_guess (%s) must be strictly greater than -1.\", guess.toString()));\n }\n function assertCashFlowsAndDatesHaveSameDimension(cashFlows, dates) {\n assert(() => cashFlows.length === dates.length && cashFlows[0].length === dates[0].length, _lt(\"The cashflow_amounts and cashflow_dates ranges must have the same dimensions.\"));\n }\n function assertCashFlowsHavePositiveAndNegativesValues(cashFlow) {\n assert(() => cashFlow.some((val) => val > 0) && cashFlow.some((val) => val < 0), _lt(\"There must be both positive and negative values in cashflow_amounts.\"));\n }\n function assertEveryDateGreaterThanFirstDateOfCashFlowDates(dates) {\n assert(() => dates.every((date) => date >= dates[0]), _lt(\"All the dates should be greater or equal to the first date in cashflow_dates (%s).\", dates[0].toString()));\n }\n\n const DEFAULT_DAY_COUNT_CONVENTION = 0;\n const DEFAULT_END_OR_BEGINNING = 0;\n const DEFAULT_FUTURE_VALUE = 0;\n const COUPON_FUNCTION_ARGS = args(`\nsettlement (date) ${_lt(\"The settlement date of the security, the date after issuance when the security is delivered to the buyer.\")}\nmaturity (date) ${_lt(\"The maturity or end date of the security, when it can be redeemed at face, or par value.\")}\nfrequency (number) ${_lt(\"The number of interest or coupon payments per year (1, 2, or 4).\")}\nday_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt(\"An indicator of what day count method to use.\")}\n`);\n /**\n * Use the Newton\u2013Raphson method to find a root of the given function in an iterative manner.\n *\n * @param func the function to find a root of\n * @param derivFunc the derivative of the function\n * @param startValue the initial value for the first iteration of the algorithm\n * @param maxIterations the maximum number of iterations\n * @param epsMax the epsilon for the root\n * @param nanFallback a function giving a fallback value to use if func(x) returns NaN. Useful if the\n * function is not defined for some range, but we know approximately where the root is when the Newton\n * algorithm ends up in this range.\n */\n function newtonMethod(func, derivFunc, startValue, maxIterations, epsMax = 1e-10, nanFallback) {\n let x = startValue;\n let newX;\n let xDelta;\n let y;\n let yEqual0 = false;\n let count = 0;\n let previousFallback = undefined;\n do {\n y = func(x);\n if (isNaN(y)) {\n assert(() => count < maxIterations && nanFallback !== undefined, _lt(`Function [[FUNCTION_NAME]] didn't find any result.`));\n count++;\n x = nanFallback(previousFallback);\n previousFallback = x;\n continue;\n }\n newX = x - y / derivFunc(x);\n xDelta = Math.abs(newX - x);\n x = newX;\n yEqual0 = xDelta < epsMax || Math.abs(y) < epsMax;\n assert(() => count < maxIterations, _lt(`Function [[FUNCTION_NAME]] didn't find any result.`));\n count++;\n } while (!yEqual0);\n return x;\n }\n // -----------------------------------------------------------------------------\n // ACCRINTM\n // -----------------------------------------------------------------------------\n const ACCRINTM = {\n description: _lt(\"Accrued interest of security paying at maturity.\"),\n args: args(`\n issue (date) ${_lt(\"The date the security was initially issued.\")}\n maturity (date) ${_lt(\"The maturity date of the security.\")}\n rate (number) ${_lt(\"The annualized rate of interest.\")}\n redemption (number) ${_lt(\"The redemption amount per 100 face value, or par.\")}\n day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt(\"An indicator of what day count method to use.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (issue, maturity, rate, redemption, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n dayCountConvention = dayCountConvention || 0;\n const start = Math.trunc(toNumber(issue));\n const end = Math.trunc(toNumber(maturity));\n const _redemption = toNumber(redemption);\n const _rate = toNumber(rate);\n const _dayCountConvention = Math.trunc(toNumber(dayCountConvention));\n assertSettlementAndIssueDatesAreValid(end, start);\n assertDayCountConventionIsValid(_dayCountConvention);\n assertRedemptionStrictlyPositive(_redemption);\n assertRateStrictlyPositive(_rate);\n const yearFrac = YEARFRAC.compute(start, end, dayCountConvention);\n return _redemption * _rate * yearFrac;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // AMORLINC\n // -----------------------------------------------------------------------------\n const AMORLINC = {\n description: _lt(\"Depreciation for an accounting period.\"),\n args: args(`\n cost (number) ${_lt(\"The initial cost of the asset.\")}\n purchase_date (date) ${_lt(\"The date the asset was purchased.\")}\n first_period_end (date) ${_lt(\"The date the first period ended.\")}\n salvage (number) ${_lt(\"The value of the asset at the end of depreciation.\")}\n period (number) ${_lt(\"The single period within life for which to calculate depreciation.\")}\n rate (number) ${_lt(\"The deprecation rate.\")}\n day_count_convention (number, optional) ${_lt(\"An indicator of what day count method to use.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (cost, purchaseDate, firstPeriodEnd, salvage, period, rate, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n dayCountConvention = dayCountConvention || 0;\n const _cost = toNumber(cost);\n const _purchaseDate = Math.trunc(toNumber(purchaseDate));\n const _firstPeriodEnd = Math.trunc(toNumber(firstPeriodEnd));\n const _salvage = toNumber(salvage);\n const _period = toNumber(period);\n const _rate = toNumber(rate);\n const _dayCountConvention = Math.trunc(toNumber(dayCountConvention));\n assertCostStrictlyPositive(_cost);\n assertSalvagePositiveOrZero(_salvage);\n assertSalvageSmallerOrEqualThanCost(_salvage, _cost);\n assertPeriodPositiveOrZero(_period);\n assertRateStrictlyPositive(_rate);\n assertDayCountConventionIsValid(_dayCountConvention);\n assert(() => _purchaseDate <= _firstPeriodEnd, _lt(\"The purchase_date (%s) must be before the first_period_end (%s).\", _purchaseDate.toString(), _firstPeriodEnd.toString()));\n /**\n * https://wiki.documentfoundation.org/Documentation/Calc_Functions/AMORLINC\n *\n * AMORLINC period 0 = cost * rate * YEARFRAC(purchase date, first period end)\n * AMORLINC period n = cost * rate\n * AMORLINC at the last period is such that the remaining deprecated cost is equal to the salvage value.\n *\n * The period is and rounded to 1 if < 1 truncated if > 1,\n *\n * Compatibility note :\n * If (purchase date) === (first period end), on GSheet the deprecation at the first period is 0, and on Excel\n * it is a full period deprecation. We choose to use the Excel behaviour.\n */\n const roundedPeriod = _period < 1 && _period > 0 ? 1 : Math.trunc(_period);\n const deprec = _cost * _rate;\n const yearFrac = YEARFRAC.compute(_purchaseDate, _firstPeriodEnd, _dayCountConvention);\n const firstDeprec = _purchaseDate === _firstPeriodEnd ? deprec : deprec * yearFrac;\n const valueAtPeriod = _cost - firstDeprec - deprec * roundedPeriod;\n if (valueAtPeriod >= _salvage) {\n return roundedPeriod === 0 ? firstDeprec : deprec;\n }\n return _salvage - valueAtPeriod < deprec ? deprec - (_salvage - valueAtPeriod) : 0;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COUPDAYS\n // -----------------------------------------------------------------------------\n const COUPDAYS = {\n description: _lt(\"Days in coupon period containing settlement date.\"),\n args: COUPON_FUNCTION_ARGS,\n returns: [\"NUMBER\"],\n compute: function (settlement, maturity, frequency, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n dayCountConvention = dayCountConvention || 0;\n const start = Math.trunc(toNumber(settlement));\n const end = Math.trunc(toNumber(maturity));\n const _frequency = Math.trunc(toNumber(frequency));\n const _dayCountConvention = Math.trunc(toNumber(dayCountConvention));\n assertMaturityAndSettlementDatesAreValid(start, end);\n assertCouponFrequencyIsValid(_frequency);\n assertDayCountConventionIsValid(_dayCountConvention);\n // https://wiki.documentfoundation.org/Documentation/Calc_Functions/COUPDAYS\n if (_dayCountConvention === 1) {\n const before = COUPPCD.compute(settlement, maturity, frequency, dayCountConvention);\n const after = COUPNCD.compute(settlement, maturity, frequency, dayCountConvention);\n return after - before;\n }\n const daysInYear = _dayCountConvention === 3 ? 365 : 360;\n return daysInYear / _frequency;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COUPDAYBS\n // -----------------------------------------------------------------------------\n const COUPDAYBS = {\n description: _lt(\"Days from settlement until next coupon.\"),\n args: COUPON_FUNCTION_ARGS,\n returns: [\"NUMBER\"],\n compute: function (settlement, maturity, frequency, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n dayCountConvention = dayCountConvention || 0;\n const start = Math.trunc(toNumber(settlement));\n const end = Math.trunc(toNumber(maturity));\n const _frequency = Math.trunc(toNumber(frequency));\n const _dayCountConvention = Math.trunc(toNumber(dayCountConvention));\n assertMaturityAndSettlementDatesAreValid(start, end);\n assertCouponFrequencyIsValid(_frequency);\n assertDayCountConventionIsValid(_dayCountConvention);\n const couponBeforeStart = COUPPCD.compute(start, end, frequency, dayCountConvention);\n if ([1, 2, 3].includes(_dayCountConvention)) {\n return start - couponBeforeStart;\n }\n if (_dayCountConvention === 4) {\n const yearFrac = getYearFrac(couponBeforeStart, start, _dayCountConvention);\n return Math.round(yearFrac * 360);\n }\n const startDate = toJsDate(start);\n const dateCouponBeforeStart = toJsDate(couponBeforeStart);\n const y1 = dateCouponBeforeStart.getFullYear();\n const y2 = startDate.getFullYear();\n const m1 = dateCouponBeforeStart.getMonth() + 1; // +1 because months in js start at 0 and it's confusing\n const m2 = startDate.getMonth() + 1;\n let d1 = dateCouponBeforeStart.getDate();\n let d2 = startDate.getDate();\n /**\n * Rules based on https://en.wikipedia.org/wiki/Day_count_convention#30/360_US\n *\n * These are slightly modified (no mention of if investment is EOM and rules order is modified),\n * but from my testing this seems the rules used by Excel/GSheet.\n */\n if (m1 === 2 &&\n m2 === 2 &&\n isLastDayOfMonth(dateCouponBeforeStart) &&\n isLastDayOfMonth(startDate)) {\n d2 = 30;\n }\n if (d2 === 31 && (d1 === 30 || d1 === 31)) {\n d2 = 30;\n }\n if (m1 === 2 && isLastDayOfMonth(dateCouponBeforeStart)) {\n d1 = 30;\n }\n if (d1 === 31) {\n d1 = 30;\n }\n return (y2 - y1) * 360 + (m2 - m1) * 30 + (d2 - d1);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COUPDAYSNC\n // -----------------------------------------------------------------------------\n const COUPDAYSNC = {\n description: _lt(\"Days from settlement until next coupon.\"),\n args: COUPON_FUNCTION_ARGS,\n returns: [\"NUMBER\"],\n compute: function (settlement, maturity, frequency, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n dayCountConvention = dayCountConvention || 0;\n const start = Math.trunc(toNumber(settlement));\n const end = Math.trunc(toNumber(maturity));\n const _frequency = Math.trunc(toNumber(frequency));\n const _dayCountConvention = Math.trunc(toNumber(dayCountConvention));\n assertMaturityAndSettlementDatesAreValid(start, end);\n assertCouponFrequencyIsValid(_frequency);\n assertDayCountConventionIsValid(_dayCountConvention);\n const couponAfterStart = COUPNCD.compute(start, end, frequency, dayCountConvention);\n if ([1, 2, 3].includes(_dayCountConvention)) {\n return couponAfterStart - start;\n }\n if (_dayCountConvention === 4) {\n const yearFrac = getYearFrac(start, couponAfterStart, _dayCountConvention);\n return Math.round(yearFrac * 360);\n }\n const coupDayBs = COUPDAYBS.compute(settlement, maturity, frequency, _dayCountConvention);\n const coupDays = COUPDAYS.compute(settlement, maturity, frequency, _dayCountConvention);\n return coupDays - coupDayBs;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COUPNCD\n // -----------------------------------------------------------------------------\n const COUPNCD = {\n description: _lt(\"Next coupon date after the settlement date.\"),\n args: COUPON_FUNCTION_ARGS,\n returns: [\"NUMBER\"],\n computeFormat: () => \"m/d/yyyy\",\n compute: function (settlement, maturity, frequency, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n dayCountConvention = dayCountConvention || 0;\n const start = Math.trunc(toNumber(settlement));\n const end = Math.trunc(toNumber(maturity));\n const _frequency = Math.trunc(toNumber(frequency));\n const _dayCountConvention = Math.trunc(toNumber(dayCountConvention));\n assertMaturityAndSettlementDatesAreValid(start, end);\n assertCouponFrequencyIsValid(_frequency);\n assertDayCountConventionIsValid(_dayCountConvention);\n const monthsPerPeriod = 12 / _frequency;\n const coupNum = COUPNUM.compute(settlement, maturity, frequency, dayCountConvention);\n const date = addMonthsToDate(toJsDate(end), -(coupNum - 1) * monthsPerPeriod, true);\n return jsDateToRoundNumber(date);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COUPNUM\n // -----------------------------------------------------------------------------\n const COUPNUM = {\n description: _lt(\"Number of coupons between settlement and maturity.\"),\n args: COUPON_FUNCTION_ARGS,\n returns: [\"NUMBER\"],\n compute: function (settlement, maturity, frequency, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n dayCountConvention = dayCountConvention || 0;\n const start = Math.trunc(toNumber(settlement));\n const end = Math.trunc(toNumber(maturity));\n const _frequency = Math.trunc(toNumber(frequency));\n const _dayCountConvention = Math.trunc(toNumber(dayCountConvention));\n assertMaturityAndSettlementDatesAreValid(start, end);\n assertCouponFrequencyIsValid(_frequency);\n assertDayCountConventionIsValid(_dayCountConvention);\n let num = 1;\n let currentDate = end;\n const monthsPerPeriod = 12 / _frequency;\n while (currentDate > start) {\n currentDate = jsDateToRoundNumber(addMonthsToDate(toJsDate(currentDate), -monthsPerPeriod, false));\n num++;\n }\n return num - 1;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COUPPCD\n // -----------------------------------------------------------------------------\n const COUPPCD = {\n description: _lt(\"Last coupon date prior to or on the settlement date.\"),\n args: COUPON_FUNCTION_ARGS,\n returns: [\"NUMBER\"],\n computeFormat: () => \"m/d/yyyy\",\n compute: function (settlement, maturity, frequency, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n dayCountConvention = dayCountConvention || 0;\n const start = Math.trunc(toNumber(settlement));\n const end = Math.trunc(toNumber(maturity));\n const _frequency = Math.trunc(toNumber(frequency));\n const _dayCountConvention = Math.trunc(toNumber(dayCountConvention));\n assertMaturityAndSettlementDatesAreValid(start, end);\n assertCouponFrequencyIsValid(_frequency);\n assertDayCountConventionIsValid(_dayCountConvention);\n const monthsPerPeriod = 12 / _frequency;\n const coupNum = COUPNUM.compute(settlement, maturity, frequency, dayCountConvention);\n const date = addMonthsToDate(toJsDate(end), -coupNum * monthsPerPeriod, true);\n return jsDateToRoundNumber(date);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // CUMIPMT\n // -----------------------------------------------------------------------------\n const CUMIPMT = {\n description: _lt(\"Cumulative interest paid over a set of periods.\"),\n args: args(`\n rate (number) ${_lt(\"The interest rate.\")}\n number_of_periods (number) ${_lt(\"The number of payments to be made.\")}\n present_value (number) ${_lt(\"The current value of the annuity.\")}\n first_period (number) ${_lt(\"The number of the payment period to begin the cumulative calculation.\")}\n last_period (number) ${_lt(\"The number of the payment period to end the cumulative calculation.\")}\n end_or_beginning (number, default=${DEFAULT_END_OR_BEGINNING}) ${_lt(\"Whether payments are due at the end (0) or beginning (1) of each period.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (rate, numberOfPeriods, presentValue, firstPeriod, lastPeriod, endOrBeginning = DEFAULT_END_OR_BEGINNING) {\n const first = toNumber(firstPeriod);\n const last = toNumber(lastPeriod);\n const _rate = toNumber(rate);\n const pv = toNumber(presentValue);\n const nOfPeriods = toNumber(numberOfPeriods);\n assertFirstAndLastPeriodsAreValid(first, last, nOfPeriods);\n assertRateStrictlyPositive(_rate);\n assertPresentValueStrictlyPositive(pv);\n let cumSum = 0;\n for (let i = first; i <= last; i++) {\n const impt = IPMT.compute(rate, i, nOfPeriods, presentValue, 0, endOrBeginning);\n cumSum += impt;\n }\n return cumSum;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // CUMPRINC\n // -----------------------------------------------------------------------------\n const CUMPRINC = {\n description: _lt(\"Cumulative principal paid over a set of periods.\"),\n args: args(`\n rate (number) ${_lt(\"The interest rate.\")}\n number_of_periods (number) ${_lt(\"The number of payments to be made.\")}\n present_value (number) ${_lt(\"The current value of the annuity.\")}\n first_period (number) ${_lt(\"The number of the payment period to begin the cumulative calculation.\")}\n last_period (number) ${_lt(\"The number of the payment period to end the cumulative calculation.\")}\n end_or_beginning (number, default=${DEFAULT_END_OR_BEGINNING}) ${_lt(\"Whether payments are due at the end (0) or beginning (1) of each period.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (rate, numberOfPeriods, presentValue, firstPeriod, lastPeriod, endOrBeginning = DEFAULT_END_OR_BEGINNING) {\n const first = toNumber(firstPeriod);\n const last = toNumber(lastPeriod);\n const _rate = toNumber(rate);\n const pv = toNumber(presentValue);\n const nOfPeriods = toNumber(numberOfPeriods);\n assertFirstAndLastPeriodsAreValid(first, last, nOfPeriods);\n assertRateStrictlyPositive(_rate);\n assertPresentValueStrictlyPositive(pv);\n let cumSum = 0;\n for (let i = first; i <= last; i++) {\n const ppmt = PPMT.compute(rate, i, nOfPeriods, presentValue, 0, endOrBeginning);\n cumSum += ppmt;\n }\n return cumSum;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DB\n // -----------------------------------------------------------------------------\n const DB = {\n description: _lt(\"Depreciation via declining balance method.\"),\n args: args(`\n cost (number) ${_lt(\"The initial cost of the asset.\")}\n salvage (number) ${_lt(\"The value of the asset at the end of depreciation.\")}\n life (number) ${_lt(\"The number of periods over which the asset is depreciated.\")}\n period (number) ${_lt(\"The single period within life for which to calculate depreciation.\")}\n month (number, optional) ${_lt(\"The number of months in the first year of depreciation.\")}\n `),\n returns: [\"NUMBER\"],\n // to do: replace by dollar format\n computeFormat: () => \"#,##0.00\",\n compute: function (cost, salvage, life, period, ...args) {\n const _cost = toNumber(cost);\n const _salvage = toNumber(salvage);\n const _life = toNumber(life);\n const _period = Math.trunc(toNumber(period));\n const _month = args.length ? Math.trunc(toNumber(args[0])) : 12;\n const lifeLimit = _life + (_month === 12 ? 0 : 1);\n assertCostPositiveOrZero(_cost);\n assertSalvagePositiveOrZero(_salvage);\n assertPeriodStrictlyPositive(_period);\n assertLifeStrictlyPositive(_life);\n assert(() => 1 <= _month && _month <= 12, _lt(\"The month (%s) must be between 1 and 12 inclusive.\", _month.toString()));\n assert(() => _period <= lifeLimit, _lt(\"The period (%s) must be less than or equal to %s.\", _period.toString(), lifeLimit.toString()));\n const monthPart = _month / 12;\n let rate = 1 - Math.pow(_salvage / _cost, 1 / _life);\n // round to 3 decimal places\n rate = Math.round(rate * 1000) / 1000;\n let before = _cost;\n let after = _cost * (1 - rate * monthPart);\n for (let i = 1; i < _period; i++) {\n before = after;\n after = before * (1 - rate);\n if (i === _life) {\n after = before * (1 - rate * (1 - monthPart));\n }\n }\n return before - after;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DDB\n // -----------------------------------------------------------------------------\n const DEFAULT_DDB_DEPRECIATION_FACTOR = 2;\n const DDB = {\n description: _lt(\"Depreciation via double-declining balance method.\"),\n args: args(`\n cost (number) ${_lt(\"The initial cost of the asset.\")}\n salvage (number) ${_lt(\"The value of the asset at the end of depreciation.\")}\n life (number) ${_lt(\"The number of periods over which the asset is depreciated.\")}\n period (number) ${_lt(\"The single period within life for which to calculate depreciation.\")}\n factor (number, default=${DEFAULT_DDB_DEPRECIATION_FACTOR}) ${_lt(\"The factor by which depreciation decreases.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: () => \"#,##0.00\",\n compute: function (cost, salvage, life, period, factor = DEFAULT_DDB_DEPRECIATION_FACTOR) {\n factor = factor || 0;\n const _cost = toNumber(cost);\n const _salvage = toNumber(salvage);\n const _life = toNumber(life);\n const _period = toNumber(period);\n const _factor = toNumber(factor);\n assertCostPositiveOrZero(_cost);\n assertSalvagePositiveOrZero(_salvage);\n assertPeriodStrictlyPositive(_period);\n assertLifeStrictlyPositive(_life);\n assertPeriodSmallerOrEqualToLife(_period, _life);\n assertDeprecationFactorStrictlyPositive(_factor);\n if (_cost === 0 || _salvage >= _cost)\n return 0;\n const deprecFactor = _factor / _life;\n if (deprecFactor > 1) {\n return period === 1 ? _cost - _salvage : 0;\n }\n if (_period <= 1) {\n return _cost * deprecFactor;\n }\n const previousCost = _cost * Math.pow(1 - deprecFactor, _period - 1);\n const nextCost = _cost * Math.pow(1 - deprecFactor, _period);\n const deprec = nextCost < _salvage ? previousCost - _salvage : previousCost - nextCost;\n return Math.max(deprec, 0);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DISC\n // -----------------------------------------------------------------------------\n const DISC = {\n description: _lt(\"Discount rate of a security based on price.\"),\n args: args(`\n settlement (date) ${_lt(\"The settlement date of the security, the date after issuance when the security is delivered to the buyer.\")}\n maturity (date) ${_lt(\"The maturity or end date of the security, when it can be redeemed at face, or par value.\")}\n price (number) ${_lt(\"The price at which the security is bought per 100 face value.\")}\n redemption (number) ${_lt(\"The redemption amount per 100 face value, or par.\")}\n day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt(\"An indicator of what day count method to use.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (settlement, maturity, price, redemption, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n dayCountConvention = dayCountConvention || 0;\n const _settlement = Math.trunc(toNumber(settlement));\n const _maturity = Math.trunc(toNumber(maturity));\n const _price = toNumber(price);\n const _redemption = toNumber(redemption);\n const _dayCountConvention = Math.trunc(toNumber(dayCountConvention));\n assertMaturityAndSettlementDatesAreValid(_settlement, _maturity);\n assertDayCountConventionIsValid(_dayCountConvention);\n assertPriceStrictlyPositive(_price);\n assertRedemptionStrictlyPositive(_redemption);\n /**\n * https://support.microsoft.com/en-us/office/disc-function-71fce9f3-3f05-4acf-a5a3-eac6ef4daa53\n *\n * B = number of days in year, depending on year basis\n * DSM = number of days from settlement to maturity\n *\n * redemption - price B\n * DISC = ____________________ * ____\n * redemption DSM\n */\n const yearsFrac = YEARFRAC.compute(_settlement, _maturity, _dayCountConvention);\n return (_redemption - _price) / _redemption / yearsFrac;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DOLLARDE\n // -----------------------------------------------------------------------------\n const DOLLARDE = {\n description: _lt(\"Convert a decimal fraction to decimal value.\"),\n args: args(`\n fractional_price (number) ${_lt(\"The price quotation given using fractional decimal conventions.\")}\n unit (number) ${_lt(\"The units of the fraction, e.g. 8 for 1/8ths or 32 for 1/32nds.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (fractionalPrice, unit) {\n const price = toNumber(fractionalPrice);\n const _unit = Math.trunc(toNumber(unit));\n assert(() => _unit > 0, _lt(\"The unit (%s) must be strictly positive.\", _unit.toString()));\n const truncatedPrice = Math.trunc(price);\n const priceFractionalPart = price - truncatedPrice;\n const frac = 10 ** Math.ceil(Math.log10(_unit)) / _unit;\n return truncatedPrice + priceFractionalPart * frac;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DOLLARFR\n // -----------------------------------------------------------------------------\n const DOLLARFR = {\n description: _lt(\"Convert a decimal value to decimal fraction.\"),\n args: args(`\n decimal_price (number) ${_lt(\"The price quotation given as a decimal value.\")}\n unit (number) ${_lt(\"The units of the desired fraction, e.g. 8 for 1/8ths or 32 for 1/32nds.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (decimalPrice, unit) {\n const price = toNumber(decimalPrice);\n const _unit = Math.trunc(toNumber(unit));\n assert(() => _unit > 0, _lt(\"The unit (%s) must be strictly positive.\", _unit.toString()));\n const truncatedPrice = Math.trunc(price);\n const priceFractionalPart = price - truncatedPrice;\n const frac = _unit / 10 ** Math.ceil(Math.log10(_unit));\n return truncatedPrice + priceFractionalPart * frac;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DURATION\n // -----------------------------------------------------------------------------\n const DURATION = {\n description: _lt(\"Number of periods for an investment to reach a value.\"),\n args: args(`\n settlement (date) ${_lt(\"The settlement date of the security, the date after issuance when the security is delivered to the buyer.\")}\n maturity (date) ${_lt(\"The maturity or end date of the security, when it can be redeemed at face, or par value.\")}\n rate (number) ${_lt(\"The annualized rate of interest.\")}\n yield (number) ${_lt(\"The expected annual yield of the security.\")}\n frequency (number) ${_lt(\"The number of interest or coupon payments per year (1, 2, or 4).\")}\n day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt(\"An indicator of what day count method to use.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (settlement, maturity, rate, securityYield, frequency, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n dayCountConvention = dayCountConvention || 0;\n const start = Math.trunc(toNumber(settlement));\n const end = Math.trunc(toNumber(maturity));\n const _rate = toNumber(rate);\n const _yield = toNumber(securityYield);\n const _frequency = Math.trunc(toNumber(frequency));\n const _dayCountConvention = Math.trunc(toNumber(dayCountConvention));\n assertMaturityAndSettlementDatesAreValid(start, end);\n assertCouponFrequencyIsValid(_frequency);\n assertDayCountConventionIsValid(_dayCountConvention);\n assert(() => _rate >= 0, _lt(\"The rate (%s) must be positive or null.\", _rate.toString()));\n assert(() => _yield >= 0, _lt(\"The yield (%s) must be positive or null.\", _yield.toString()));\n const years = YEARFRAC.compute(start, end, _dayCountConvention);\n const timeFirstYear = years - Math.trunc(years) || 1 / _frequency;\n const nbrCoupons = Math.ceil(years * _frequency);\n // The DURATION function return the Macaulay duration\n // See example: https://en.wikipedia.org/wiki/Bond_duration#Formulas\n const cashFlowFromCoupon = _rate / _frequency;\n const yieldPerPeriod = _yield / _frequency;\n let count = 0;\n let sum = 0;\n for (let i = 1; i <= nbrCoupons; i++) {\n const cashFlowPerPeriod = cashFlowFromCoupon + (i === nbrCoupons ? 1 : 0);\n const presentValuePerPeriod = cashFlowPerPeriod / (1 + yieldPerPeriod) ** i;\n sum += (timeFirstYear + (i - 1) / _frequency) * presentValuePerPeriod;\n count += presentValuePerPeriod;\n }\n return count === 0 ? 0 : sum / count;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // EFFECT\n // -----------------------------------------------------------------------------\n const EFFECT = {\n description: _lt(\"Annual effective interest rate.\"),\n args: args(`\n nominal_rate (number) ${_lt(\"The nominal interest rate per year.\")}\n periods_per_year (number) ${_lt(\"The number of compounding periods per year.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (nominal_rate, periods_per_year) {\n const nominal = toNumber(nominal_rate);\n const periods = Math.trunc(toNumber(periods_per_year));\n assert(() => nominal > 0, _lt(\"The nominal rate (%s) must be strictly greater than 0.\", nominal.toString()));\n assert(() => periods > 0, _lt(\"The number of periods by year (%s) must strictly greater than 0.\", periods.toString()));\n // https://en.wikipedia.org/wiki/Nominal_interest_rate#Nominal_versus_effective_interest_rate\n return Math.pow(1 + nominal / periods, periods) - 1;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // FV\n // -----------------------------------------------------------------------------\n const DEFAULT_PRESENT_VALUE = 0;\n const FV = {\n description: _lt(\"Future value of an annuity investment.\"),\n args: args(`\n rate (number) ${_lt(\"The interest rate.\")}\n number_of_periods (number) ${_lt(\"The number of payments to be made.\")}\n payment_amount (number) ${_lt(\"The amount per period to be paid.\")}\n present_value (number, default=${DEFAULT_PRESENT_VALUE}) ${_lt(\"The current value of the annuity.\")}\n end_or_beginning (number, default=${DEFAULT_END_OR_BEGINNING}) ${_lt(\"Whether payments are due at the end (0) or beginning (1) of each period.\")}\n `),\n returns: [\"NUMBER\"],\n // to do: replace by dollar format\n computeFormat: () => \"#,##0.00\",\n compute: function (rate, numberOfPeriods, paymentAmount, presentValue = DEFAULT_PRESENT_VALUE, endOrBeginning = DEFAULT_END_OR_BEGINNING) {\n presentValue = presentValue || 0;\n endOrBeginning = endOrBeginning || 0;\n const r = toNumber(rate);\n const n = toNumber(numberOfPeriods);\n const p = toNumber(paymentAmount);\n const pv = toNumber(presentValue);\n const type = toBoolean(endOrBeginning) ? 1 : 0;\n return r ? -pv * (1 + r) ** n - (p * (1 + r * type) * ((1 + r) ** n - 1)) / r : -(pv + p * n);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // FVSCHEDULE\n // -----------------------------------------------------------------------------\n const FVSCHEDULE = {\n description: _lt(\"Future value of principal from series of rates.\"),\n args: args(`\n principal (number) ${_lt(\"The amount of initial capital or value to compound against.\")}\n rate_schedule (number, range) ${_lt(\"A series of interest rates to compound against the principal.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (principalAmount, rateSchedule) {\n const principal = toNumber(principalAmount);\n return reduceAny([rateSchedule], (acc, rate) => acc * (1 + toNumber(rate)), principal);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // INTRATE\n // -----------------------------------------------------------------------------\n const INTRATE = {\n description: _lt(\"Calculates effective interest rate.\"),\n args: args(`\n settlement (date) ${_lt(\"The settlement date of the security, the date after issuance when the security is delivered to the buyer.\")}\n maturity (date) ${_lt(\"The maturity or end date of the security, when it can be redeemed at face, or par value.\")}\n investment (number) ${_lt(\"The amount invested in the security.\")}\n redemption (number) ${_lt(\"The amount to be received at maturity.\")}\n day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt(\"An indicator of what day count method to use.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (settlement, maturity, investment, redemption, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n const _settlement = Math.trunc(toNumber(settlement));\n const _maturity = Math.trunc(toNumber(maturity));\n const _redemption = toNumber(redemption);\n const _investment = toNumber(investment);\n assertMaturityAndSettlementDatesAreValid(_settlement, _maturity);\n assertInvestmentStrictlyPositive(_investment);\n assertRedemptionStrictlyPositive(_redemption);\n /**\n * https://wiki.documentfoundation.org/Documentation/Calc_Functions/INTRATE\n *\n * (Redemption - Investment) / Investment\n * INTRATE = _________________________________________\n * YEARFRAC(settlement, maturity, basis)\n */\n const yearFrac = YEARFRAC.compute(_settlement, _maturity, dayCountConvention);\n return (_redemption - _investment) / _investment / yearFrac;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // IPMT\n // -----------------------------------------------------------------------------\n const IPMT = {\n description: _lt(\"Payment on the principal of an investment.\"),\n args: args(`\n rate (number) ${_lt(\"The annualized rate of interest.\")}\n period (number) ${_lt(\"The amortization period, in terms of number of periods.\")}\n number_of_periods (number) ${_lt(\"The number of payments to be made.\")}\n present_value (number) ${_lt(\"The current value of the annuity.\")}\n future_value (number, default=${DEFAULT_FUTURE_VALUE}) ${_lt(\"The future value remaining after the final payment has been made.\")}\n end_or_beginning (number, default=${DEFAULT_END_OR_BEGINNING}) ${_lt(\"Whether payments are due at the end (0) or beginning (1) of each period.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: () => \"#,##0.00\",\n compute: function (rate, currentPeriod, numberOfPeriods, presentValue, futureValue = DEFAULT_FUTURE_VALUE, endOrBeginning = DEFAULT_END_OR_BEGINNING) {\n const payment = PMT.compute(rate, numberOfPeriods, presentValue, futureValue, endOrBeginning);\n const ppmt = PPMT.compute(rate, currentPeriod, numberOfPeriods, presentValue, futureValue, endOrBeginning);\n return payment - ppmt;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // IRR\n // -----------------------------------------------------------------------------\n const DEFAULT_RATE_GUESS = 0.1;\n const IRR = {\n description: _lt(\"Internal rate of return given periodic cashflows.\"),\n args: args(`\n cashflow_amounts (number, range) ${_lt(\"An array or range containing the income or payments associated with the investment.\")}\n rate_guess (number, default=${DEFAULT_RATE_GUESS}) ${_lt(\"An estimate for what the internal rate of return will be.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: () => \"0%\",\n compute: function (cashFlowAmounts, rateGuess = DEFAULT_RATE_GUESS) {\n const _rateGuess = toNumber(rateGuess);\n assertRateGuessStrictlyGreaterThanMinusOne(_rateGuess);\n // check that values contains at least one positive value and one negative value\n // and extract number present in the cashFlowAmount argument\n let positive = false;\n let negative = false;\n let amounts = [];\n visitNumbers([cashFlowAmounts], (amount) => {\n if (amount > 0)\n positive = true;\n if (amount < 0)\n negative = true;\n amounts.push(amount);\n });\n assert(() => positive && negative, _lt(\"The cashflow_amounts must include negative and positive values.\"));\n const firstAmount = amounts.shift();\n // The result of IRR is the rate at which the NPV() function will return zero with the given values.\n // This algorithm uses the Newton's method on the NPV function to determine the result\n // Newton's method: https://en.wikipedia.org/wiki/Newton%27s_method\n // As the NPV function isn't continuous, we apply the Newton's method on the numerator of the NPV formula.\n function npvNumerator(rate, startValue, values) {\n const nbrValue = values.length;\n let i = 0;\n return values.reduce((acc, v) => {\n i++;\n return acc + v * rate ** (nbrValue - i);\n }, startValue * rate ** nbrValue);\n }\n function npvNumeratorDeriv(rate, startValue, values) {\n const nbrValue = values.length;\n let i = 0;\n return values.reduce((acc, v) => {\n i++;\n return acc + v * (nbrValue - i) * rate ** (nbrValue - i - 1);\n }, startValue * nbrValue * rate ** (nbrValue - 1));\n }\n function func(x) {\n return npvNumerator(x, firstAmount, amounts);\n }\n function derivFunc(x) {\n return npvNumeratorDeriv(x, firstAmount, amounts);\n }\n return newtonMethod(func, derivFunc, _rateGuess + 1, 20, 1e-5) - 1;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ISPMT\n // -----------------------------------------------------------------------------\n const ISPMT = {\n description: _lt(\"Returns the interest paid at a particular period of an investment.\"),\n args: args(`\n rate (number) ${_lt(\"The interest rate.\")}\n period (number) ${_lt(\"The period for which you want to view the interest payment.\")}\n number_of_periods (number) ${_lt(\"The number of payments to be made.\")}\n present_value (number) ${_lt(\"The current value of the annuity.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (rate, currentPeriod, numberOfPeriods, presentValue) {\n const interestRate = toNumber(rate);\n const period = toNumber(currentPeriod);\n const nOfPeriods = toNumber(numberOfPeriods);\n const investment = toNumber(presentValue);\n assert(() => nOfPeriods !== 0, _lt(\"The number of periods must be different than 0.\", nOfPeriods.toString()));\n const currentInvestment = investment - investment * (period / nOfPeriods);\n return -1 * currentInvestment * interestRate;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // MDURATION\n // -----------------------------------------------------------------------------\n const MDURATION = {\n description: _lt(\"Modified Macaulay duration.\"),\n args: args(`\n settlement (date) ${_lt(\"The settlement date of the security, the date after issuance when the security is delivered to the buyer.\")}\n maturity (date) ${_lt(\"The maturity or end date of the security, when it can be redeemed at face, or par value.\")}\n rate (number) ${_lt(\"The annualized rate of interest.\")}\n yield (number) ${_lt(\"The expected annual yield of the security.\")}\n frequency (number) ${_lt(\"The number of interest or coupon payments per year (1, 2, or 4).\")}\n day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt(\"An indicator of what day count method to use.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (settlement, maturity, rate, securityYield, frequency, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n const duration = DURATION.compute(settlement, maturity, rate, securityYield, frequency, dayCountConvention);\n const y = toNumber(securityYield);\n const k = Math.trunc(toNumber(frequency));\n return duration / (1 + y / k);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // MIRR\n // -----------------------------------------------------------------------------\n const MIRR = {\n description: _lt(\"Modified internal rate of return.\"),\n args: args(`\n cashflow_amounts (range) ${_lt(\"A range containing the income or payments associated with the investment. The array should contain bot payments and incomes.\")}\n financing_rate (number) ${_lt(\"The interest rate paid on funds invested.\")}\n reinvestment_return_rate (number) ${_lt(\"The return (as a percentage) earned on reinvestment of income received from the investment.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (cashflowAmount, financingRate, reinvestmentRate) {\n const fRate = toNumber(financingRate);\n const rRate = toNumber(reinvestmentRate);\n const cashFlow = transpose2dArray(cashflowAmount).flat().filter(isDefined$1).map(toNumber);\n const n = cashFlow.length;\n /**\n * https://en.wikipedia.org/wiki/Modified_internal_rate_of_return\n *\n * / FV(positive cash flows, reinvestment rate) \\ ^ (1 / (n - 1))\n * MIRR = | ___________________________________________ | - 1\n * \\ - PV(negative cash flows, finance rate) /\n *\n * with n the number of cash flows.\n *\n * You can compute FV and PV as :\n *\n * FV = SUM [ (cashFlow[i]>0 ? cashFlow[i] : 0) * (1 + rRate)**(n - i-1) ]\n * i= 0 => n\n *\n * PV = SUM [ (cashFlow[i]<0 ? cashFlow[i] : 0) / (1 + fRate)**i ]\n * i= 0 => n\n */\n let fv = 0;\n let pv = 0;\n for (const i of range(0, n)) {\n const amount = cashFlow[i];\n if (amount >= 0) {\n fv += amount * (rRate + 1) ** (n - i - 1);\n }\n else {\n pv += amount / (fRate + 1) ** i;\n }\n }\n assert(() => pv !== 0 && fv !== 0, _lt(\"There must be both positive and negative values in cashflow_amounts.\"));\n const exponent = 1 / (n - 1);\n return (-fv / pv) ** exponent - 1;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // NOMINAL\n // -----------------------------------------------------------------------------\n const NOMINAL = {\n description: _lt(\"Annual nominal interest rate.\"),\n args: args(`\n effective_rate (number) ${_lt(\"The effective interest rate per year.\")}\n periods_per_year (number) ${_lt(\"The number of compounding periods per year.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (effective_rate, periods_per_year) {\n const effective = toNumber(effective_rate);\n const periods = Math.trunc(toNumber(periods_per_year));\n assert(() => effective > 0, _lt(\"The effective rate (%s) must must strictly greater than 0.\", effective.toString()));\n assert(() => periods > 0, _lt(\"The number of periods by year (%s) must strictly greater than 0.\", periods.toString()));\n // https://en.wikipedia.org/wiki/Nominal_interest_rate#Nominal_versus_effective_interest_rate\n return (Math.pow(effective + 1, 1 / periods) - 1) * periods;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // NPER\n // -----------------------------------------------------------------------------\n const NPER = {\n description: _lt(\"Number of payment periods for an investment.\"),\n args: args(`\n rate (number) ${_lt(\"The interest rate.\")}\n payment_amount (number) ${_lt(\"The amount of each payment made.\")}\n present_value (number) ${_lt(\"The current value of the annuity.\")}\n future_value (number, default=${DEFAULT_FUTURE_VALUE}) ${_lt(\"The future value remaining after the final payment has been made.\")}\n end_or_beginning (number, default=${DEFAULT_END_OR_BEGINNING}) ${_lt(\"Whether payments are due at the end (0) or beginning (1) of each period.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (rate, paymentAmount, presentValue, futureValue = DEFAULT_FUTURE_VALUE, endOrBeginning = DEFAULT_END_OR_BEGINNING) {\n futureValue = futureValue || 0;\n endOrBeginning = endOrBeginning || 0;\n const r = toNumber(rate);\n const p = toNumber(paymentAmount);\n const pv = toNumber(presentValue);\n const fv = toNumber(futureValue);\n const t = toBoolean(endOrBeginning) ? 1 : 0;\n /**\n * https://wiki.documentfoundation.org/Documentation/Calc_Functions/NPER\n *\n * 0 = pv * (1 + r)^N + fv + [ p * (1 + r * t) * ((1 + r)^N - 1) ] / r\n *\n * We solve the equation for N:\n *\n * with C = [ p * (1 + r * t)] / r and\n * R = 1 + r\n *\n * => 0 = pv * R^N + C * R^N - C + fv\n * <=> (C - fv) = R^N * (pv + C)\n * <=> log[(C - fv) / (pv + C)] = N * log(R)\n */\n if (r === 0) {\n return -(fv + pv) / p;\n }\n const c = (p * (1 + r * t)) / r;\n return Math.log((c - fv) / (pv + c)) / Math.log(1 + r);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // NPV\n // -----------------------------------------------------------------------------\n function npvResult(r, startValue, values) {\n let i = 0;\n return reduceNumbers(values, (acc, v) => {\n i++;\n return acc + v / (1 + r) ** i;\n }, startValue);\n }\n const NPV = {\n description: _lt(\"The net present value of an investment based on a series of periodic cash flows and a discount rate.\"),\n args: args(`\n discount (number) ${_lt(\"The discount rate of the investment over one period.\")}\n cashflow1 (number, range) ${_lt(\"The first future cash flow.\")}\n cashflow2 (number, range, repeating) ${_lt(\"Additional future cash flows.\")}\n `),\n returns: [\"NUMBER\"],\n // to do: replace by dollar format\n computeFormat: () => \"#,##0.00\",\n compute: function (discount, ...values) {\n const _discount = toNumber(discount);\n assert(() => _discount !== -1, _lt(\"The discount (%s) must be different from -1.\", _discount.toString()));\n return npvResult(_discount, 0, values);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // PDURATION\n // -----------------------------------------------------------------------------\n const PDURATION = {\n description: _lt(\"Computes the number of periods needed for an investment to reach a value.\"),\n args: args(`\n rate (number) ${_lt(\"The rate at which the investment grows each period.\")}\n present_value (number) ${_lt(\"The investment's current value.\")}\n future_value (number) ${_lt(\"The investment's desired future value.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (rate, presentValue, futureValue) {\n const _rate = toNumber(rate);\n const _presentValue = toNumber(presentValue);\n const _futureValue = toNumber(futureValue);\n assertRateStrictlyPositive(_rate);\n assert(() => _presentValue > 0, _lt(\"The present_value (%s) must be strictly positive.\", _presentValue.toString()));\n assert(() => _futureValue > 0, _lt(\"The future_value (%s) must be strictly positive.\", _futureValue.toString()));\n return (Math.log(_futureValue) - Math.log(_presentValue)) / Math.log(1 + _rate);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // PMT\n // -----------------------------------------------------------------------------\n const PMT = {\n description: _lt(\"Periodic payment for an annuity investment.\"),\n args: args(`\n rate (number) ${_lt(\"The annualized rate of interest.\")}\n number_of_periods (number) ${_lt(\"The number of payments to be made.\")}\n present_value (number) ${_lt(\"The current value of the annuity.\")}\n future_value (number, default=${DEFAULT_FUTURE_VALUE}) ${_lt(\"The future value remaining after the final payment has been made.\")}\n end_or_beginning (number, default=${DEFAULT_END_OR_BEGINNING}) ${_lt(\"Whether payments are due at the end (0) or beginning (1) of each period.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: () => \"#,##0.00\",\n compute: function (rate, numberOfPeriods, presentValue, futureValue = DEFAULT_FUTURE_VALUE, endOrBeginning = DEFAULT_END_OR_BEGINNING) {\n futureValue = futureValue || 0;\n endOrBeginning = endOrBeginning || 0;\n const n = toNumber(numberOfPeriods);\n const r = toNumber(rate);\n const t = toBoolean(endOrBeginning) ? 1 : 0;\n let fv = toNumber(futureValue);\n let pv = toNumber(presentValue);\n assertNumberOfPeriodsStrictlyPositive(n);\n /**\n * https://wiki.documentfoundation.org/Documentation/Calc_Functions/PMT\n *\n * 0 = pv * (1 + r)^N + fv + [ p * (1 + r * t) * ((1 + r)^N - 1) ] / r\n *\n * We simply the equation for p\n */\n if (r === 0) {\n return -(fv + pv) / n;\n }\n let payment = -(pv * (1 + r) ** n + fv);\n payment = (payment * r) / ((1 + r * t) * ((1 + r) ** n - 1));\n return payment;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // PPMT\n // -----------------------------------------------------------------------------\n const PPMT = {\n description: _lt(\"Payment on the principal of an investment.\"),\n args: args(`\n rate (number) ${_lt(\"The annualized rate of interest.\")}\n period (number) ${_lt(\"The amortization period, in terms of number of periods.\")}\n number_of_periods (number) ${_lt(\"The number of payments to be made.\")}\n present_value (number) ${_lt(\"The current value of the annuity.\")}\n future_value (number, default=${DEFAULT_FUTURE_VALUE}) ${_lt(\"The future value remaining after the final payment has been made.\")}\n end_or_beginning (number, default=${DEFAULT_END_OR_BEGINNING}) ${_lt(\"Whether payments are due at the end (0) or beginning (1) of each period.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: () => \"#,##0.00\",\n compute: function (rate, currentPeriod, numberOfPeriods, presentValue, futureValue = DEFAULT_FUTURE_VALUE, endOrBeginning = DEFAULT_END_OR_BEGINNING) {\n futureValue = futureValue || 0;\n endOrBeginning = endOrBeginning || 0;\n const n = toNumber(numberOfPeriods);\n const r = toNumber(rate);\n const period = toNumber(currentPeriod);\n const type = toBoolean(endOrBeginning) ? 1 : 0;\n const fv = toNumber(futureValue);\n const pv = toNumber(presentValue);\n assertNumberOfPeriodsStrictlyPositive(n);\n assert(() => period > 0 && period <= n, _lt(\"The period must be between 1 and number_of_periods\", n.toString()));\n const payment = PMT.compute(r, n, pv, fv, endOrBeginning);\n if (type === 1 && period === 1)\n return payment;\n const eqPeriod = type === 0 ? period - 1 : period - 2;\n const eqPv = pv + payment * type;\n const capitalAtPeriod = -FV.compute(r, eqPeriod, payment, eqPv, 0);\n const currentInterest = capitalAtPeriod * r;\n return payment + currentInterest;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // PV\n // -----------------------------------------------------------------------------\n const PV = {\n description: _lt(\"Present value of an annuity investment.\"),\n args: args(`\n rate (number) ${_lt(\"The interest rate.\")}\n number_of_periods (number) ${_lt(\"The number of payments to be made.\")}\n payment_amount (number) ${_lt(\"The amount per period to be paid.\")}\n future_value (number, default=${DEFAULT_FUTURE_VALUE}) ${_lt(\"The future value remaining after the final payment has been made.\")}\n end_or_beginning (number, default=${DEFAULT_END_OR_BEGINNING}) ${_lt(\"Whether payments are due at the end (0) or beginning (1) of each period.\")}\n `),\n returns: [\"NUMBER\"],\n // to do: replace by dollar format\n computeFormat: () => \"#,##0.00\",\n compute: function (rate, numberOfPeriods, paymentAmount, futureValue = DEFAULT_FUTURE_VALUE, endOrBeginning = DEFAULT_END_OR_BEGINNING) {\n futureValue = futureValue || 0;\n endOrBeginning = endOrBeginning || 0;\n const r = toNumber(rate);\n const n = toNumber(numberOfPeriods);\n const p = toNumber(paymentAmount);\n const fv = toNumber(futureValue);\n const type = toBoolean(endOrBeginning) ? 1 : 0;\n // https://wiki.documentfoundation.org/Documentation/Calc_Functions/PV\n return r ? -((p * (1 + r * type) * ((1 + r) ** n - 1)) / r + fv) / (1 + r) ** n : -(fv + p * n);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // PRICE\n // -----------------------------------------------------------------------------\n const PRICE = {\n description: _lt(\"Price of a security paying periodic interest.\"),\n args: args(`\n settlement (date) ${_lt(\"The settlement date of the security, the date after issuance when the security is delivered to the buyer.\")}\n maturity (date) ${_lt(\"The maturity or end date of the security, when it can be redeemed at face, or par value.\")}\n rate (number) ${_lt(\"The annualized rate of interest.\")}\n yield (number) ${_lt(\"The expected annual yield of the security.\")}\n redemption (number) ${_lt(\"The redemption amount per 100 face value, or par.\")}\n frequency (number) ${_lt(\"The number of interest or coupon payments per year (1, 2, or 4).\")}\n day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt(\"An indicator of what day count method to use.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (settlement, maturity, rate, securityYield, redemption, frequency, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n dayCountConvention = dayCountConvention || 0;\n const _settlement = Math.trunc(toNumber(settlement));\n const _maturity = Math.trunc(toNumber(maturity));\n const _rate = toNumber(rate);\n const _yield = toNumber(securityYield);\n const _redemption = toNumber(redemption);\n const _frequency = Math.trunc(toNumber(frequency));\n const _dayCountConvention = Math.trunc(toNumber(dayCountConvention));\n assertMaturityAndSettlementDatesAreValid(_settlement, _maturity);\n assertCouponFrequencyIsValid(_frequency);\n assertDayCountConventionIsValid(_dayCountConvention);\n assert(() => _rate >= 0, _lt(\"The rate (%s) must be positive or null.\", _rate.toString()));\n assert(() => _yield >= 0, _lt(\"The yield (%s) must be positive or null.\", _yield.toString()));\n assertRedemptionStrictlyPositive(_redemption);\n const years = YEARFRAC.compute(_settlement, _maturity, _dayCountConvention);\n const nbrRealCoupons = years * _frequency;\n const nbrFullCoupons = Math.ceil(nbrRealCoupons);\n const timeFirstCoupon = nbrRealCoupons - Math.floor(nbrRealCoupons) || 1;\n const yieldFactorPerPeriod = 1 + _yield / _frequency;\n const cashFlowFromCoupon = (100 * _rate) / _frequency;\n if (nbrFullCoupons === 1) {\n return ((cashFlowFromCoupon + _redemption) / ((timeFirstCoupon * _yield) / _frequency + 1) -\n cashFlowFromCoupon * (1 - timeFirstCoupon));\n }\n let cashFlowsPresentValue = 0;\n for (let i = 1; i <= nbrFullCoupons; i++) {\n cashFlowsPresentValue +=\n cashFlowFromCoupon / yieldFactorPerPeriod ** (i - 1 + timeFirstCoupon);\n }\n const redemptionPresentValue = _redemption / yieldFactorPerPeriod ** (nbrFullCoupons - 1 + timeFirstCoupon);\n return (redemptionPresentValue + cashFlowsPresentValue - cashFlowFromCoupon * (1 - timeFirstCoupon));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // PRICEDISC\n // -----------------------------------------------------------------------------\n const PRICEDISC = {\n description: _lt(\"Price of a discount security.\"),\n args: args(`\n settlement (date) ${_lt(\"The settlement date of the security, the date after issuance when the security is delivered to the buyer.\")}\n maturity (date) ${_lt(\"The maturity or end date of the security, when it can be redeemed at face, or par value.\")}\n discount (number) ${_lt(\"The discount rate of the security at time of purchase.\")}\n redemption (number) ${_lt(\"The redemption amount per 100 face value, or par.\")}\n day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt(\"An indicator of what day count method to use.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (settlement, maturity, discount, redemption, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n dayCountConvention = dayCountConvention || 0;\n const _settlement = Math.trunc(toNumber(settlement));\n const _maturity = Math.trunc(toNumber(maturity));\n const _discount = toNumber(discount);\n const _redemption = toNumber(redemption);\n const _dayCountConvention = Math.trunc(toNumber(dayCountConvention));\n assertMaturityAndSettlementDatesAreValid(_settlement, _maturity);\n assertDayCountConventionIsValid(_dayCountConvention);\n assertDiscountStrictlyPositive(_discount);\n assertRedemptionStrictlyPositive(_redemption);\n /**\n * https://support.microsoft.com/en-us/office/pricedisc-function-d06ad7c1-380e-4be7-9fd9-75e3079acfd3\n *\n * B = number of days in year, depending on year basis\n * DSM = number of days from settlement to maturity\n *\n * PRICEDISC = redemption - discount * redemption * (DSM/B)\n */\n const yearsFrac = YEARFRAC.compute(_settlement, _maturity, _dayCountConvention);\n return _redemption - _discount * _redemption * yearsFrac;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // PRICEMAT\n // -----------------------------------------------------------------------------\n const PRICEMAT = {\n description: _lt(\"Calculates the price of a security paying interest at maturity, based on expected yield.\"),\n args: args(`\n settlement (date) ${_lt(\"The settlement date of the security, the date after issuance when the security is delivered to the buyer.\")}\n maturity (date) ${_lt(\"The maturity or end date of the security, when it can be redeemed at face, or par value.\")}\n issue (date) ${_lt(\"The date the security was initially issued.\")}\n rate (number) ${_lt(\"The annualized rate of interest.\")}\n yield (number) ${_lt(\"The expected annual yield of the security.\")}\n day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt(\"An indicator of what day count method to use.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (settlement, maturity, issue, rate, securityYield, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n dayCountConvention = dayCountConvention || 0;\n const _settlement = Math.trunc(toNumber(settlement));\n const _maturity = Math.trunc(toNumber(maturity));\n const _issue = Math.trunc(toNumber(issue));\n const _rate = toNumber(rate);\n const _yield = toNumber(securityYield);\n const _dayCount = Math.trunc(toNumber(dayCountConvention));\n assertSettlementAndIssueDatesAreValid(_settlement, _issue);\n assertMaturityAndSettlementDatesAreValid(_settlement, _maturity);\n assertDayCountConventionIsValid(_dayCount);\n assert(() => _rate >= 0, _lt(\"The rate (%s) must be positive or null.\", _rate.toString()));\n assert(() => _yield >= 0, _lt(\"The yield (%s) must be positive or null.\", _yield.toString()));\n /**\n * https://support.microsoft.com/en-us/office/pricemat-function-52c3b4da-bc7e-476a-989f-a95f675cae77\n *\n * B = number of days in year, depending on year basis\n * DSM = number of days from settlement to maturity\n * DIM = number of days from issue to maturity\n * DIS = number of days from issue to settlement\n *\n * 100 + (DIM/B * rate * 100)\n * PRICEMAT = __________________________ - (DIS/B * rate * 100)\n * 1 + (DSM/B * yield)\n *\n * The ratios number_of_days / days_in_year are computed using the YEARFRAC function, that handle\n * differences due to day count conventions.\n *\n * Compatibility note :\n *\n * Contrary to GSheet and OpenOffice, Excel doesn't seems to always use its own YEARFRAC function\n * to compute PRICEMAT, and give different values for some combinations of dates and day count\n * conventions ( notably for leap years and dayCountConvention = 1 (Actual/Actual)).\n *\n * Our function PRICEMAT give us the same results as LibreOffice Calc.\n * Google Sheet use the formula with YEARFRAC, but its YEARFRAC function results are different\n * from the results of Excel/LibreOffice, thus we get different values with PRICEMAT.\n *\n */\n const settlementToMaturity = YEARFRAC.compute(_settlement, _maturity, _dayCount);\n const issueToSettlement = YEARFRAC.compute(_settlement, _issue, _dayCount);\n const issueToMaturity = YEARFRAC.compute(_issue, _maturity, _dayCount);\n const numerator = 100 + issueToMaturity * _rate * 100;\n const denominator = 1 + settlementToMaturity * _yield;\n const term2 = issueToSettlement * _rate * 100;\n return numerator / denominator - term2;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // RATE\n // -----------------------------------------------------------------------------\n const RATE_GUESS_DEFAULT = 0.1;\n const RATE = {\n description: _lt(\"Interest rate of an annuity investment.\"),\n args: args(`\n number_of_periods (number) ${_lt(\"The number of payments to be made.\")}\n payment_per_period (number) ${_lt(\"The amount per period to be paid.\")}\n present_value (number) ${_lt(\"The current value of the annuity.\")}\n future_value (number, default=${DEFAULT_FUTURE_VALUE}) ${_lt(\"The future value remaining after the final payment has been made.\")}\n end_or_beginning (number, default=${DEFAULT_END_OR_BEGINNING}) ${_lt(\"Whether payments are due at the end (0) or beginning (1) of each period.\")}\n rate_guess (number, default=${RATE_GUESS_DEFAULT}) ${_lt(\"An estimate for what the interest rate will be.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: () => \"0%\",\n compute: function (numberOfPeriods, paymentPerPeriod, presentValue, futureValue = DEFAULT_FUTURE_VALUE, endOrBeginning = DEFAULT_END_OR_BEGINNING, rateGuess = RATE_GUESS_DEFAULT) {\n futureValue = futureValue || 0;\n endOrBeginning = endOrBeginning || 0;\n rateGuess = rateGuess || RATE_GUESS_DEFAULT;\n const n = toNumber(numberOfPeriods);\n const payment = toNumber(paymentPerPeriod);\n const type = toBoolean(endOrBeginning) ? 1 : 0;\n const guess = toNumber(rateGuess);\n let fv = toNumber(futureValue);\n let pv = toNumber(presentValue);\n assertNumberOfPeriodsStrictlyPositive(n);\n assert(() => [payment, pv, fv].some((val) => val > 0) && [payment, pv, fv].some((val) => val < 0), _lt(\"There must be both positive and negative values in [payment_amount, present_value, future_value].\", n.toString()));\n assertRateGuessStrictlyGreaterThanMinusOne(guess);\n fv -= payment * type;\n pv += payment * type;\n // https://github.com/apache/openoffice/blob/trunk/main/sc/source/core/tool/interpr2.cxx\n const func = (rate) => {\n const powN = Math.pow(1 + rate, n);\n const intResult = (powN - 1) / rate;\n return fv + pv * powN + payment * intResult;\n };\n const derivFunc = (rate) => {\n const powNMinus1 = Math.pow(1 + rate, n - 1);\n const powN = Math.pow(1 + rate, n);\n const intResult = (powN - 1) / rate;\n const intResultDeriv = (n * powNMinus1) / rate - intResult / rate;\n const fTermDerivation = pv * n * powNMinus1 + payment * intResultDeriv;\n return fTermDerivation;\n };\n return newtonMethod(func, derivFunc, guess, 40, 1e-5);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // RECEIVED\n // -----------------------------------------------------------------------------\n const RECEIVED = {\n description: _lt(\"Amount received at maturity for a security.\"),\n args: args(`\n settlement (date) ${_lt(\"The settlement date of the security, the date after issuance when the security is delivered to the buyer.\")}\n maturity (date) ${_lt(\"The maturity or end date of the security, when it can be redeemed at face, or par value.\")}\n investment (number) ${_lt(\"The amount invested (irrespective of face value of each security).\")}\n discount (number) ${_lt(\"The discount rate of the security invested in.\")}\n day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt(\"An indicator of what day count method to use.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (settlement, maturity, investment, discount, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n dayCountConvention = dayCountConvention || 0;\n const _settlement = Math.trunc(toNumber(settlement));\n const _maturity = Math.trunc(toNumber(maturity));\n const _investment = toNumber(investment);\n const _discount = toNumber(discount);\n const _dayCountConvention = Math.trunc(toNumber(dayCountConvention));\n assertMaturityAndSettlementDatesAreValid(_settlement, _maturity);\n assertDayCountConventionIsValid(_dayCountConvention);\n assertInvestmentStrictlyPositive(_investment);\n assertDiscountStrictlyPositive(_discount);\n /**\n * https://support.microsoft.com/en-us/office/received-function-7a3f8b93-6611-4f81-8576-828312c9b5e5\n *\n * investment\n * RECEIVED = _________________________\n * 1 - discount * DSM / B\n *\n * with DSM = number of days from settlement to maturity and B = number of days in a year\n *\n * The ratio DSM/B can be computed with the YEARFRAC function to take the dayCountConvention into account.\n */\n const yearsFrac = YEARFRAC.compute(_settlement, _maturity, _dayCountConvention);\n return _investment / (1 - _discount * yearsFrac);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // RRI\n // -----------------------------------------------------------------------------\n const RRI = {\n description: _lt(\"Computes the rate needed for an investment to reach a specific value within a specific number of periods.\"),\n args: args(`\n number_of_periods (number) ${_lt(\"The number of periods.\")}\n present_value (number) ${_lt(\"The present value of the investment.\")}\n future_value (number) ${_lt(\"The future value of the investment.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (numberOfPeriods, presentValue, futureValue) {\n const n = toNumber(numberOfPeriods);\n const pv = toNumber(presentValue);\n const fv = toNumber(futureValue);\n assertNumberOfPeriodsStrictlyPositive(n);\n /**\n * https://support.microsoft.com/en-us/office/rri-function-6f5822d8-7ef1-4233-944c-79e8172930f4\n *\n * RRI = (future value / present value) ^ (1 / number of periods) - 1\n */\n return (fv / pv) ** (1 / n) - 1;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // SLN\n // -----------------------------------------------------------------------------\n const SLN = {\n description: _lt(\"Depreciation of an asset using the straight-line method.\"),\n args: args(`\n cost (number) ${_lt(\"The initial cost of the asset.\")}\n salvage (number) ${_lt(\"The value of the asset at the end of depreciation.\")}\n life (number) ${_lt(\"The number of periods over which the asset is depreciated.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: () => \"#,##0.00\",\n compute: function (cost, salvage, life) {\n const _cost = toNumber(cost);\n const _salvage = toNumber(salvage);\n const _life = toNumber(life);\n // No assertion is done on the values of the arguments to be compatible with Excel/Gsheet that don't check the values.\n // It's up to the user to make sure the arguments make sense, which is good design because the user is smart.\n return (_cost - _salvage) / _life;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // SYD\n // -----------------------------------------------------------------------------\n const SYD = {\n description: _lt(\"Depreciation via sum of years digit method.\"),\n args: args(`\n cost (number) ${_lt(\"The initial cost of the asset.\")}\n salvage (number) ${_lt(\"The value of the asset at the end of depreciation.\")}\n life (number) ${_lt(\"The number of periods over which the asset is depreciated.\")}\n period (number) ${_lt(\"The single period within life for which to calculate depreciation.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: () => \"#,##0.00\",\n compute: function (cost, salvage, life, period) {\n const _cost = toNumber(cost);\n const _salvage = toNumber(salvage);\n const _life = toNumber(life);\n const _period = toNumber(period);\n assertPeriodStrictlyPositive(_period);\n assertLifeStrictlyPositive(_life);\n assertPeriodSmallerOrEqualToLife(_period, _life);\n /**\n * This deprecation method use the sum of digits of the periods of the life as the deprecation factor.\n * For example for a life = 5, we have a deprecation factor or 1 + 2 + 3 + 4 + 5 = 15 = life * (life + 1) / 2 = F.\n *\n * The deprecation for a period p is then computed based on F and the remaining lifetime at the period P.\n *\n * deprecation = (cost - salvage) * (number of remaining periods / F)\n */\n const deprecFactor = (_life * (_life + 1)) / 2;\n const remainingPeriods = _life - _period + 1;\n return (_cost - _salvage) * (remainingPeriods / deprecFactor);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // TBILLPRICE\n // -----------------------------------------------------------------------------\n const TBILLPRICE = {\n description: _lt(\"Price of a US Treasury bill.\"),\n args: args(`\n settlement (date) ${_lt(\"The settlement date of the security, the date after issuance when the security is delivered to the buyer.\")}\n maturity (date) ${_lt(\"The maturity or end date of the security, when it can be redeemed at face, or par value.\")}\n discount (number) ${_lt(\"The discount rate of the bill at time of purchase.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (settlement, maturity, discount) {\n const start = Math.trunc(toNumber(settlement));\n const end = Math.trunc(toNumber(maturity));\n const disc = toNumber(discount);\n assertMaturityAndSettlementDatesAreValid(start, end);\n assertSettlementLessThanOneYearBeforeMaturity(start, end);\n assertDiscountStrictlyPositive(disc);\n assertDiscountStrictlySmallerThanOne(disc);\n /**\n * https://support.microsoft.com/en-us/office/tbillprice-function-eacca992-c29d-425a-9eb8-0513fe6035a2\n *\n * TBILLPRICE = 100 * (1 - discount * DSM / 360)\n *\n * with DSM = number of days from settlement to maturity\n *\n * The ratio DSM/360 can be computed with the YEARFRAC function with dayCountConvention = 2 (actual/360).\n */\n const yearFrac = YEARFRAC.compute(start, end, 2);\n return 100 * (1 - disc * yearFrac);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // TBILLEQ\n // -----------------------------------------------------------------------------\n const TBILLEQ = {\n description: _lt(\"Equivalent rate of return for a US Treasury bill.\"),\n args: args(`\n settlement (date) ${_lt(\"The settlement date of the security, the date after issuance when the security is delivered to the buyer.\")}\n maturity (date) ${_lt(\"The maturity or end date of the security, when it can be redeemed at face, or par value.\")}\n discount (number) ${_lt(\"The discount rate of the bill at time of purchase.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (settlement, maturity, discount) {\n const start = Math.trunc(toNumber(settlement));\n const end = Math.trunc(toNumber(maturity));\n const disc = toNumber(discount);\n assertMaturityAndSettlementDatesAreValid(start, end);\n assertSettlementLessThanOneYearBeforeMaturity(start, end);\n assertDiscountStrictlyPositive(disc);\n assertDiscountStrictlySmallerThanOne(disc);\n /**\n * https://support.microsoft.com/en-us/office/tbilleq-function-2ab72d90-9b4d-4efe-9fc2-0f81f2c19c8c\n *\n * 365 * discount\n * TBILLEQ = ________________________\n * 360 - discount * DSM\n *\n * with DSM = number of days from settlement to maturity\n *\n * What is not indicated in the Excel documentation is that this formula only works for duration between settlement\n * and maturity that are less than 6 months (182 days). This is because US Treasury bills use semi-annual interest,\n * and thus we have to take into account the compound interest for the calculation.\n *\n * For this case, the formula becomes (Treasury Securities and Derivatives, by Frank J. Fabozzi, page 49)\n *\n * -2X + 2* SQRT[ X\u00b2 - (2X - 1) * (1 - 100/p) ]\n * TBILLEQ = ________________________________________________\n * 2X - 1\n *\n * with X = DSM / (number of days in a year),\n * and p is the price, computed with TBILLPRICE\n *\n * Note that from my tests in Excel, we take (number of days in a year) = 366 ONLY if DSM is 366, not if\n * the settlement year is a leap year.\n *\n */\n const nDays = DAYS.compute(end, start);\n if (nDays <= 182) {\n return (365 * disc) / (360 - disc * nDays);\n }\n const p = TBILLPRICE.compute(start, end, disc) / 100;\n const daysInYear = nDays === 366 ? 366 : 365;\n const x = nDays / daysInYear;\n const num = -2 * x + 2 * Math.sqrt(x ** 2 - (2 * x - 1) * (1 - 1 / p));\n const denom = 2 * x - 1;\n return num / denom;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // TBILLYIELD\n // -----------------------------------------------------------------------------\n const TBILLYIELD = {\n description: _lt(\"The yield of a US Treasury bill based on price.\"),\n args: args(`\n settlement (date) ${_lt(\"The settlement date of the security, the date after issuance when the security is delivered to the buyer.\")}\n maturity (date) ${_lt(\"The maturity or end date of the security, when it can be redeemed at face, or par value.\")}\n price (number) ${_lt(\"The price at which the security is bought per 100 face value.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (settlement, maturity, price) {\n const start = Math.trunc(toNumber(settlement));\n const end = Math.trunc(toNumber(maturity));\n const p = toNumber(price);\n assertMaturityAndSettlementDatesAreValid(start, end);\n assertSettlementLessThanOneYearBeforeMaturity(start, end);\n assertPriceStrictlyPositive(p);\n /**\n * https://support.microsoft.com/en-us/office/tbillyield-function-6d381232-f4b0-4cd5-8e97-45b9c03468ba\n *\n * 100 - price 360\n * TBILLYIELD = ____________ * _____\n * price DSM\n *\n * with DSM = number of days from settlement to maturity\n *\n * The ratio DSM/360 can be computed with the YEARFRAC function with dayCountConvention = 2 (actual/360).\n *\n */\n const yearFrac = YEARFRAC.compute(start, end, 2);\n return ((100 - p) / p) * (1 / yearFrac);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // VDB\n // -----------------------------------------------------------------------------\n const DEFAULT_VDB_NO_SWITCH = false;\n const VDB = {\n description: _lt(\"Variable declining balance. WARNING : does not handle decimal periods.\"),\n args: args(`\n cost (number) ${_lt(\"The initial cost of the asset.\")}\n salvage (number) ${_lt(\"The value of the asset at the end of depreciation.\")}\n life (number) ${_lt(\"The number of periods over which the asset is depreciated.\")}\n start (number) ${_lt(\"Starting period to calculate depreciation.\")}\n end (number) ${_lt(\"Ending period to calculate depreciation.\")}\n factor (number, default=${DEFAULT_DDB_DEPRECIATION_FACTOR}) ${_lt(\"The number of months in the first year of depreciation.\")}\n no_switch (number, default=${DEFAULT_VDB_NO_SWITCH}) ${_lt(\"Whether to switch to straight-line depreciation when the depreciation is greater than the declining balance calculation.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (cost, salvage, life, startPeriod, endPeriod, factor = DEFAULT_DDB_DEPRECIATION_FACTOR, noSwitch = DEFAULT_VDB_NO_SWITCH) {\n factor = factor || 0;\n const _cost = toNumber(cost);\n const _salvage = toNumber(salvage);\n const _life = toNumber(life);\n /* TODO : handle decimal periods\n * on end_period it looks like it is a simple linear function, but I cannot understand exactly how\n * decimals periods are handled with start_period.\n */\n const _startPeriod = Math.trunc(toNumber(startPeriod));\n const _endPeriod = Math.trunc(toNumber(endPeriod));\n const _factor = toNumber(factor);\n const _noSwitch = toBoolean(noSwitch);\n assertCostPositiveOrZero(_cost);\n assertSalvagePositiveOrZero(_salvage);\n assertStartAndEndPeriodAreValid(_startPeriod, _endPeriod, _life);\n assertDeprecationFactorStrictlyPositive(_factor);\n if (_cost === 0)\n return 0;\n if (_salvage >= _cost) {\n return _startPeriod < 1 ? _cost - _salvage : 0;\n }\n const doubleDeprecFactor = _factor / _life;\n if (doubleDeprecFactor >= 1) {\n return _startPeriod < 1 ? _cost - _salvage : 0;\n }\n let previousCost = _cost;\n let currentDeprec = 0;\n let resultDeprec = 0;\n let isLinearDeprec = false;\n for (let i = 0; i < _endPeriod; i++) {\n // compute the current deprecation, or keep the last one if we reached a stage of linear deprecation\n if (!isLinearDeprec || _noSwitch) {\n const doubleDeprec = previousCost * doubleDeprecFactor;\n const remainingPeriods = _life - i;\n const linearDeprec = (previousCost - _salvage) / remainingPeriods;\n if (!_noSwitch && linearDeprec > doubleDeprec) {\n isLinearDeprec = true;\n currentDeprec = linearDeprec;\n }\n else {\n currentDeprec = doubleDeprec;\n }\n }\n const nextCost = Math.max(previousCost - currentDeprec, _salvage);\n if (i >= _startPeriod) {\n resultDeprec += previousCost - nextCost;\n }\n previousCost = nextCost;\n }\n return resultDeprec;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // XIRR\n // -----------------------------------------------------------------------------\n const XIRR = {\n description: _lt(\"Internal rate of return given non-periodic cash flows.\"),\n args: args(`\n cashflow_amounts (range) ${_lt(\"An range containing the income or payments associated with the investment.\")}\n cashflow_dates (range) ${_lt(\"An range with dates corresponding to the cash flows in cashflow_amounts.\")}\n rate_guess (number, default=${RATE_GUESS_DEFAULT}) ${_lt(\"An estimate for what the internal rate of return will be.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (cashflowAmounts, cashflowDates, rateGuess = RATE_GUESS_DEFAULT) {\n rateGuess = rateGuess || 0;\n const guess = toNumber(rateGuess);\n const _cashFlows = cashflowAmounts.flat().map(toNumber);\n const _dates = cashflowDates.flat().map(toNumber);\n assertCashFlowsAndDatesHaveSameDimension(cashflowAmounts, cashflowDates);\n assertCashFlowsHavePositiveAndNegativesValues(_cashFlows);\n assertEveryDateGreaterThanFirstDateOfCashFlowDates(_dates);\n assertRateGuessStrictlyGreaterThanMinusOne(guess);\n const map = new Map();\n for (const i of range(0, _dates.length)) {\n const date = _dates[i];\n if (map.has(date))\n map.set(date, map.get(date) + _cashFlows[i]);\n else\n map.set(date, _cashFlows[i]);\n }\n const dates = Array.from(map.keys());\n const values = dates.map((date) => map.get(date));\n /**\n * https://support.microsoft.com/en-us/office/xirr-function-de1242ec-6477-445b-b11b-a303ad9adc9d\n *\n * The rate is computed iteratively by trying to solve the equation\n *\n *\n * 0 = SUM [ P_i * (1 + rate) ^((d_0 - d_i) / 365) ] + P_0\n * i = 1 => n\n *\n * with P_i = price number i\n * d_i = date number i\n *\n * This function is not defined for rate < -1. For the case where we get rates < -1 in the Newton method, add\n * a fallback for a number very close to -1 to continue the Newton method.\n *\n */\n const func = (rate) => {\n let value = values[0];\n for (const i of range(1, values.length)) {\n const dateDiff = (dates[0] - dates[i]) / 365;\n value += values[i] * (1 + rate) ** dateDiff;\n }\n return value;\n };\n const derivFunc = (rate) => {\n let deriv = 0;\n for (const i of range(1, values.length)) {\n const dateDiff = (dates[0] - dates[i]) / 365;\n deriv += dateDiff * values[i] * (1 + rate) ** (dateDiff - 1);\n }\n return deriv;\n };\n const nanFallback = (previousFallback) => {\n // -0.9 => -0.99 => -0.999 => ...\n if (!previousFallback)\n return -0.9;\n return previousFallback / 10 - 0.9;\n };\n return newtonMethod(func, derivFunc, guess, 40, 1e-5, nanFallback);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // XNPV\n // -----------------------------------------------------------------------------\n const XNPV = {\n description: _lt(\"Net present value given to non-periodic cash flows..\"),\n args: args(`\n discount (number) ${_lt(\"The discount rate of the investment over one period.\")}\n cashflow_amounts (number, range) ${_lt(\"An range containing the income or payments associated with the investment.\")}\n cashflow_dates (number, range) ${_lt(\"An range with dates corresponding to the cash flows in cashflow_amounts.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (discount, cashflowAmounts, cashflowDates) {\n const rate = toNumber(discount);\n const _cashFlows = Array.isArray(cashflowAmounts)\n ? cashflowAmounts.flat().map(strictToNumber)\n : [strictToNumber(cashflowAmounts)];\n const _dates = Array.isArray(cashflowDates)\n ? cashflowDates.flat().map(strictToNumber)\n : [strictToNumber(cashflowDates)];\n if (Array.isArray(cashflowDates) && Array.isArray(cashflowAmounts)) {\n assertCashFlowsAndDatesHaveSameDimension(cashflowAmounts, cashflowDates);\n }\n else {\n assert(() => _cashFlows.length === _dates.length, _lt(\"There must be the same number of values in cashflow_amounts and cashflow_dates.\"));\n }\n assertEveryDateGreaterThanFirstDateOfCashFlowDates(_dates);\n assertRateStrictlyPositive(rate);\n if (_cashFlows.length === 1)\n return _cashFlows[0];\n // aggregate values of the same date\n const map = new Map();\n for (const i of range(0, _dates.length)) {\n const date = _dates[i];\n if (map.has(date))\n map.set(date, map.get(date) + _cashFlows[i]);\n else\n map.set(date, _cashFlows[i]);\n }\n const dates = Array.from(map.keys());\n const values = dates.map((date) => map.get(date));\n /**\n * https://support.microsoft.com/en-us/office/xirr-function-de1242ec-6477-445b-b11b-a303ad9adc9d\n *\n * The present value is computed using\n *\n *\n * NPV = SUM [ P_i *(1 + rate) ^((d_0 - d_i) / 365) ] + P_0\n * i = 1 => n\n *\n * with P_i = price number i\n * d_i = date number i\n *\n *\n */\n let pv = values[0];\n for (const i of range(1, values.length)) {\n const dateDiff = (dates[0] - dates[i]) / 365;\n pv += values[i] * (1 + rate) ** dateDiff;\n }\n return pv;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // YIELD\n // -----------------------------------------------------------------------------\n const YIELD = {\n description: _lt(\"Annual yield of a security paying periodic interest.\"),\n args: args(`\n settlement (date) ${_lt(\"The settlement date of the security, the date after issuance when the security is delivered to the buyer.\")}\n maturity (date) ${_lt(\"The maturity or end date of the security, when it can be redeemed at face, or par value.\")}\n rate (number) ${_lt(\"The annualized rate of interest.\")}\n price (number) ${_lt(\"The price at which the security is bought per 100 face value.\")}\n redemption (number) ${_lt(\"The redemption amount per 100 face value, or par.\")}\n frequency (number) ${_lt(\"The number of interest or coupon payments per year (1, 2, or 4).\")}\n day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt(\"An indicator of what day count method to use.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (settlement, maturity, rate, price, redemption, frequency, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n dayCountConvention = dayCountConvention || 0;\n const _settlement = Math.trunc(toNumber(settlement));\n const _maturity = Math.trunc(toNumber(maturity));\n const _rate = toNumber(rate);\n const _price = toNumber(price);\n const _redemption = toNumber(redemption);\n const _frequency = Math.trunc(toNumber(frequency));\n const _dayCountConvention = Math.trunc(toNumber(dayCountConvention));\n assertMaturityAndSettlementDatesAreValid(_settlement, _maturity);\n assertCouponFrequencyIsValid(_frequency);\n assertDayCountConventionIsValid(_dayCountConvention);\n assert(() => _rate >= 0, _lt(\"The rate (%s) must be positive or null.\", _rate.toString()));\n assertPriceStrictlyPositive(_price);\n assertRedemptionStrictlyPositive(_redemption);\n const years = YEARFRAC.compute(_settlement, _maturity, _dayCountConvention);\n const nbrRealCoupons = years * _frequency;\n const nbrFullCoupons = Math.ceil(nbrRealCoupons);\n const timeFirstCoupon = nbrRealCoupons - Math.floor(nbrRealCoupons) || 1;\n const cashFlowFromCoupon = (100 * _rate) / _frequency;\n if (nbrFullCoupons === 1) {\n const subPart = _price + cashFlowFromCoupon * (1 - timeFirstCoupon);\n return (((_redemption + cashFlowFromCoupon - subPart) * _frequency * (1 / timeFirstCoupon)) /\n subPart);\n }\n // The result of YIELD function is the yield at which the PRICE function will return the given price.\n // This algorithm uses the Newton's method on the PRICE function to determine the result.\n // Newton's method: https://en.wikipedia.org/wiki/Newton%27s_method\n // As the PRICE function isn't continuous, we apply the Newton's method on the numerator of the PRICE formula.\n // For simplicity, it is not yield but yieldFactorPerPeriod (= 1 + yield / frequency) which will be calibrated in Newton's method.\n // yield can be deduced from yieldFactorPerPeriod in sequence.\n function priceNumerator(price, timeFirstCoupon, nbrFullCoupons, yieldFactorPerPeriod, cashFlowFromCoupon, redemption) {\n let result = redemption -\n (price + cashFlowFromCoupon * (1 - timeFirstCoupon)) *\n yieldFactorPerPeriod ** (nbrFullCoupons - 1 + timeFirstCoupon);\n for (let i = 1; i <= nbrFullCoupons; i++) {\n result += cashFlowFromCoupon * yieldFactorPerPeriod ** (i - 1);\n }\n return result;\n }\n function priceNumeratorDeriv(price, timeFirstCoupon, nbrFullCoupons, yieldFactorPerPeriod, cashFlowFromCoupon) {\n let result = -(price + cashFlowFromCoupon * (1 - timeFirstCoupon)) *\n (nbrFullCoupons - 1 + timeFirstCoupon) *\n yieldFactorPerPeriod ** (nbrFullCoupons - 2 + timeFirstCoupon);\n for (let i = 1; i <= nbrFullCoupons; i++) {\n result += cashFlowFromCoupon * (i - 1) * yieldFactorPerPeriod ** (i - 2);\n }\n return result;\n }\n function func(x) {\n return priceNumerator(_price, timeFirstCoupon, nbrFullCoupons, x, cashFlowFromCoupon, _redemption);\n }\n function derivFunc(x) {\n return priceNumeratorDeriv(_price, timeFirstCoupon, nbrFullCoupons, x, cashFlowFromCoupon);\n }\n const initYield = _rate + 1;\n const initYieldFactorPerPeriod = 1 + initYield / _frequency;\n const methodResult = newtonMethod(func, derivFunc, initYieldFactorPerPeriod, 100, 1e-5);\n return (methodResult - 1) * _frequency;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // YIELDDISC\n // -----------------------------------------------------------------------------\n const YIELDDISC = {\n description: _lt(\"Annual yield of a discount security.\"),\n args: args(`\n settlement (date) ${_lt(\"The settlement date of the security, the date after issuance when the security is delivered to the buyer.\")}\n maturity (date) ${_lt(\"The maturity or end date of the security, when it can be redeemed at face, or par value.\")}\n price (number) ${_lt(\"The price at which the security is bought per 100 face value.\")}\n redemption (number) ${_lt(\"The redemption amount per 100 face value, or par.\")}\n day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt(\"An indicator of what day count method to use.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (settlement, maturity, price, redemption, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n dayCountConvention = dayCountConvention || 0;\n const _settlement = Math.trunc(toNumber(settlement));\n const _maturity = Math.trunc(toNumber(maturity));\n const _price = toNumber(price);\n const _redemption = toNumber(redemption);\n const _dayCountConvention = Math.trunc(toNumber(dayCountConvention));\n assertMaturityAndSettlementDatesAreValid(_settlement, _maturity);\n assertDayCountConventionIsValid(_dayCountConvention);\n assertPriceStrictlyPositive(_price);\n assertRedemptionStrictlyPositive(_redemption);\n /**\n * https://wiki.documentfoundation.org/Documentation/Calc_Functions/YIELDDISC\n *\n * (redemption / price) - 1\n * YIELDDISC = _____________________________________\n * YEARFRAC(settlement, maturity, basis)\n */\n const yearFrac = YEARFRAC.compute(settlement, maturity, dayCountConvention);\n return (_redemption / _price - 1) / yearFrac;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // YIELDMAT\n // -----------------------------------------------------------------------------\n const YIELDMAT = {\n description: _lt(\"Annual yield of a security paying interest at maturity.\"),\n args: args(`\n settlement (date) ${_lt(\"The settlement date of the security, the date after issuance when the security is delivered to the buyer.\")}\n maturity (date) ${_lt(\"The maturity or end date of the security, when it can be redeemed at face, or par value.\")}\n issue (date) ${_lt(\"The date the security was initially issued.\")}\n rate (number) ${_lt(\"The annualized rate of interest.\")}\n price (number) ${_lt(\"The price at which the security is bought.\")}\n day_count_convention (number, default=${DEFAULT_DAY_COUNT_CONVENTION} ) ${_lt(\"An indicator of what day count method to use.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (settlement, maturity, issue, rate, price, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {\n dayCountConvention = dayCountConvention || 0;\n const _settlement = Math.trunc(toNumber(settlement));\n const _maturity = Math.trunc(toNumber(maturity));\n const _issue = Math.trunc(toNumber(issue));\n const _rate = toNumber(rate);\n const _price = toNumber(price);\n const _dayCountConvention = Math.trunc(toNumber(dayCountConvention));\n assertMaturityAndSettlementDatesAreValid(_settlement, _maturity);\n assertDayCountConventionIsValid(_dayCountConvention);\n assert(() => _settlement >= _issue, _lt(\"The settlement (%s) must be greater than or equal to the issue (%s).\", _settlement.toString(), _issue.toString()));\n assert(() => _rate >= 0, _lt(\"The rate (%s) must be positive or null.\", _rate.toString()));\n assertPriceStrictlyPositive(_price);\n const issueToMaturity = YEARFRAC.compute(_issue, _maturity, _dayCountConvention);\n const issueToSettlement = YEARFRAC.compute(_issue, _settlement, _dayCountConvention);\n const settlementToMaturity = YEARFRAC.compute(_settlement, _maturity, _dayCountConvention);\n const numerator = (100 * (1 + _rate * issueToMaturity)) / (_price + 100 * _rate * issueToSettlement) - 1;\n return numerator / settlementToMaturity;\n },\n isExported: true,\n };\n\n var financial = /*#__PURE__*/Object.freeze({\n __proto__: null,\n ACCRINTM: ACCRINTM,\n AMORLINC: AMORLINC,\n COUPDAYS: COUPDAYS,\n COUPDAYBS: COUPDAYBS,\n COUPDAYSNC: COUPDAYSNC,\n COUPNCD: COUPNCD,\n COUPNUM: COUPNUM,\n COUPPCD: COUPPCD,\n CUMIPMT: CUMIPMT,\n CUMPRINC: CUMPRINC,\n DB: DB,\n DDB: DDB,\n DISC: DISC,\n DOLLARDE: DOLLARDE,\n DOLLARFR: DOLLARFR,\n DURATION: DURATION,\n EFFECT: EFFECT,\n FV: FV,\n FVSCHEDULE: FVSCHEDULE,\n INTRATE: INTRATE,\n IPMT: IPMT,\n IRR: IRR,\n ISPMT: ISPMT,\n MDURATION: MDURATION,\n MIRR: MIRR,\n NOMINAL: NOMINAL,\n NPER: NPER,\n NPV: NPV,\n PDURATION: PDURATION,\n PMT: PMT,\n PPMT: PPMT,\n PV: PV,\n PRICE: PRICE,\n PRICEDISC: PRICEDISC,\n PRICEMAT: PRICEMAT,\n RATE: RATE,\n RECEIVED: RECEIVED,\n RRI: RRI,\n SLN: SLN,\n SYD: SYD,\n TBILLPRICE: TBILLPRICE,\n TBILLEQ: TBILLEQ,\n TBILLYIELD: TBILLYIELD,\n VDB: VDB,\n XIRR: XIRR,\n XNPV: XNPV,\n YIELD: YIELD,\n YIELDDISC: YIELDDISC,\n YIELDMAT: YIELDMAT\n });\n\n // -----------------------------------------------------------------------------\n // ISERR\n // -----------------------------------------------------------------------------\n const ISERR = {\n description: _lt(\"Whether a value is an error other than #N/A.\"),\n args: args(`value (any, lazy) ${_lt(\"The value to be verified as an error type.\")}`),\n returns: [\"BOOLEAN\"],\n compute: function (value) {\n try {\n value();\n return false;\n }\n catch (e) {\n return (e === null || e === void 0 ? void 0 : e.errorType) != CellErrorType.NotAvailable;\n }\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ISERROR\n // -----------------------------------------------------------------------------\n const ISERROR = {\n description: _lt(\"Whether a value is an error.\"),\n args: args(`value (any, lazy) ${_lt(\"The value to be verified as an error type.\")}`),\n returns: [\"BOOLEAN\"],\n compute: function (value) {\n try {\n value();\n return false;\n }\n catch (e) {\n return true;\n }\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ISLOGICAL\n // -----------------------------------------------------------------------------\n const ISLOGICAL = {\n description: _lt(\"Whether a value is `true` or `false`.\"),\n args: args(`value (any, lazy) ${_lt(\"The value to be verified as a logical TRUE or FALSE.\")}`),\n returns: [\"BOOLEAN\"],\n compute: function (value) {\n try {\n return typeof value() === \"boolean\";\n }\n catch (e) {\n return false;\n }\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ISNA\n // -----------------------------------------------------------------------------\n const ISNA = {\n description: _lt(\"Whether a value is the error #N/A.\"),\n args: args(`value (any, lazy) ${_lt(\"The value to be verified as an error type.\")}`),\n returns: [\"BOOLEAN\"],\n compute: function (value) {\n try {\n value();\n return false;\n }\n catch (e) {\n return (e === null || e === void 0 ? void 0 : e.errorType) == CellErrorType.NotAvailable;\n }\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ISNONTEXT\n // -----------------------------------------------------------------------------\n const ISNONTEXT = {\n description: _lt(\"Whether a value is non-textual.\"),\n args: args(`value (any, lazy) ${_lt(\"The value to be checked.\")}`),\n returns: [\"BOOLEAN\"],\n compute: function (value) {\n try {\n return typeof value() !== \"string\";\n }\n catch (e) {\n return true;\n }\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ISNUMBER\n // -----------------------------------------------------------------------------\n const ISNUMBER = {\n description: _lt(\"Whether a value is a number.\"),\n args: args(`value (any, lazy) ${_lt(\"The value to be verified as a number.\")}`),\n returns: [\"BOOLEAN\"],\n compute: function (value) {\n try {\n return typeof value() === \"number\";\n }\n catch (e) {\n return false;\n }\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ISTEXT\n // -----------------------------------------------------------------------------\n const ISTEXT = {\n description: _lt(\"Whether a value is text.\"),\n args: args(`value (any, lazy) ${_lt(\"The value to be verified as text.\")}`),\n returns: [\"BOOLEAN\"],\n compute: function (value) {\n try {\n return typeof value() === \"string\";\n }\n catch (e) {\n return false;\n }\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ISBLANK\n // -----------------------------------------------------------------------------\n const ISBLANK = {\n description: _lt(\"Whether the referenced cell is empty\"),\n args: args(`value (any, lazy) ${_lt(\"Reference to the cell that will be checked for emptiness.\")}`),\n returns: [\"BOOLEAN\"],\n compute: function (value) {\n try {\n const val = value();\n return val === null;\n }\n catch (e) {\n return false;\n }\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // NA\n // -----------------------------------------------------------------------------\n const NA = {\n description: _lt(\"Returns the error value #N/A.\"),\n args: args(``),\n returns: [\"BOOLEAN\"],\n compute: function (value) {\n throw new NotAvailableError();\n },\n isExported: true,\n };\n\n var info = /*#__PURE__*/Object.freeze({\n __proto__: null,\n ISERR: ISERR,\n ISERROR: ISERROR,\n ISLOGICAL: ISLOGICAL,\n ISNA: ISNA,\n ISNONTEXT: ISNONTEXT,\n ISNUMBER: ISNUMBER,\n ISTEXT: ISTEXT,\n ISBLANK: ISBLANK,\n NA: NA\n });\n\n // -----------------------------------------------------------------------------\n // AND\n // -----------------------------------------------------------------------------\n const AND = {\n description: _lt(\"Logical `and` operator.\"),\n args: args(`\n logical_expression1 (boolean, range) ${_lt(\"An expression or reference to a cell containing an expression that represents some logical value, i.e. TRUE or FALSE, or an expression that can be coerced to a logical value.\")}\n logical_expression2 (boolean, range, repeating) ${_lt(\"More expressions that represent logical values.\")}\n `),\n returns: [\"BOOLEAN\"],\n compute: function (...logicalExpressions) {\n let foundBoolean = false;\n let acc = true;\n conditionalVisitBoolean(logicalExpressions, (arg) => {\n foundBoolean = true;\n acc = acc && arg;\n return acc;\n });\n assert(() => foundBoolean, _lt(`[[FUNCTION_NAME]] has no valid input data.`));\n return acc;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // IF\n // -----------------------------------------------------------------------------\n const IF = {\n description: _lt(\"Returns value depending on logical expression.\"),\n args: args(`\n logical_expression (boolean) ${_lt(\"An expression or reference to a cell containing an expression that represents some logical value, i.e. TRUE or FALSE.\")}\n value_if_true (any, lazy) ${_lt(\"The value the function returns if logical_expression is TRUE.\")}\n value_if_false (any, lazy, default=FALSE) ${_lt(\"The value the function returns if logical_expression is FALSE.\")}\n `),\n returns: [\"ANY\"],\n compute: function (logicalExpression, valueIfTrue, valueIfFalse = () => false) {\n const result = toBoolean(logicalExpression) ? valueIfTrue() : valueIfFalse();\n return result === null || result === undefined ? \"\" : result;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // IFERROR\n // -----------------------------------------------------------------------------\n const IFERROR = {\n description: _lt(\"Value if it is not an error, otherwise 2nd argument.\"),\n args: args(`\n value (any, lazy) ${_lt(\"The value to return if value itself is not an error.\")}\n value_if_error (any, lazy, default=${_lt(\"An empty value\")}) ${_lt(\"The value the function returns if value is an error.\")}\n `),\n returns: [\"ANY\"],\n computeFormat: (value, valueIfError = () => ({ value: \"\" })) => {\n var _a;\n try {\n return value().format;\n }\n catch (e) {\n return (_a = valueIfError()) === null || _a === void 0 ? void 0 : _a.format;\n }\n },\n compute: function (value, valueIfError = () => \"\") {\n let result;\n try {\n result = value();\n }\n catch (e) {\n result = valueIfError();\n }\n return result === null || result === undefined ? \"\" : result;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // IFNA\n // -----------------------------------------------------------------------------\n const IFNA = {\n description: _lt(\"Value if it is not an #N/A error, otherwise 2nd argument.\"),\n args: args(`\n value (any, lazy) ${_lt(\"The value to return if value itself is not #N/A an error.\")}\n value_if_error (any, lazy, default=${_lt(\"An empty value\")}) ${_lt(\"The value the function returns if value is an #N/A error.\")}\n `),\n returns: [\"ANY\"],\n compute: function (value, valueIfError = () => \"\") {\n let result;\n try {\n result = value();\n }\n catch (e) {\n if (e.errorType === CellErrorType.NotAvailable) {\n result = valueIfError();\n }\n else {\n result = value();\n }\n }\n return result === null || result === undefined ? \"\" : result;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // IFS\n // -----------------------------------------------------------------------------\n const IFS = {\n description: _lt(\"Returns a value depending on multiple logical expressions.\"),\n args: args(`\n condition1 (boolean, lazy) ${_lt(\"The first condition to be evaluated. This can be a boolean, a number, an array, or a reference to any of those.\")}\n value1 (any, lazy) ${_lt(\"The returned value if condition1 is TRUE.\")}\n condition2 (boolean, lazy, repeating) ${_lt(\"Additional conditions to be evaluated if the previous ones are FALSE.\")}\n value2 (any, lazy, repeating) ${_lt(\"Additional values to be returned if their corresponding conditions are TRUE.\")}\n `),\n returns: [\"ANY\"],\n compute: function (...values) {\n assert(() => values.length % 2 === 0, _lt(`Wrong number of arguments. Expected an even number of arguments.`));\n for (let n = 0; n < values.length - 1; n += 2) {\n if (toBoolean(values[n]())) {\n const returnValue = values[n + 1]();\n return returnValue !== null ? returnValue : \"\";\n }\n }\n throw new Error(_lt(`No match.`));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // NOT\n // -----------------------------------------------------------------------------\n const NOT = {\n description: _lt(\"Returns opposite of provided logical value.\"),\n args: args(`logical_expression (boolean) ${_lt(\"An expression or reference to a cell holding an expression that represents some logical value.\")}\n `),\n returns: [\"BOOLEAN\"],\n compute: function (logicalExpression) {\n return !toBoolean(logicalExpression);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // OR\n // -----------------------------------------------------------------------------\n const OR = {\n description: _lt(\"Logical `or` operator.\"),\n args: args(`\n logical_expression1 (boolean, range) ${_lt(\"An expression or reference to a cell containing an expression that represents some logical value, i.e. TRUE or FALSE, or an expression that can be coerced to a logical value.\")}\n logical_expression2 (boolean, range, repeating) ${_lt(\"More expressions that evaluate to logical values.\")}\n `),\n returns: [\"BOOLEAN\"],\n compute: function (...logicalExpressions) {\n let foundBoolean = false;\n let acc = false;\n conditionalVisitBoolean(logicalExpressions, (arg) => {\n foundBoolean = true;\n acc = acc || arg;\n return !acc;\n });\n assert(() => foundBoolean, _lt(`[[FUNCTION_NAME]] has no valid input data.`));\n return acc;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // XOR\n // -----------------------------------------------------------------------------\n const XOR = {\n description: _lt(\"Logical `xor` operator.\"),\n args: args(`\n logical_expression1 (boolean, range) ${_lt(\"An expression or reference to a cell containing an expression that represents some logical value, i.e. TRUE or FALSE, or an expression that can be coerced to a logical value.\")}\n logical_expression2 (boolean, range, repeating) ${_lt(\"More expressions that evaluate to logical values.\")}\n `),\n returns: [\"BOOLEAN\"],\n compute: function (...logicalExpressions) {\n let foundBoolean = false;\n let acc = false;\n conditionalVisitBoolean(logicalExpressions, (arg) => {\n foundBoolean = true;\n acc = acc ? !arg : arg;\n return true; // no stop condition\n });\n assert(() => foundBoolean, _lt(`[[FUNCTION_NAME]] has no valid input data.`));\n return acc;\n },\n isExported: true,\n };\n\n var logical = /*#__PURE__*/Object.freeze({\n __proto__: null,\n AND: AND,\n IF: IF,\n IFERROR: IFERROR,\n IFNA: IFNA,\n IFS: IFS,\n NOT: NOT,\n OR: OR,\n XOR: XOR\n });\n\n const DEFAULT_IS_SORTED = true;\n const DEFAULT_MATCH_MODE = 0;\n const DEFAULT_SEARCH_MODE = 1;\n function assertAvailable(variable, searchKey) {\n if (variable === undefined) {\n throw new NotAvailableError(_lt(\"Did not find value '%s' in [[FUNCTION_NAME]] evaluation.\", toString(searchKey)));\n }\n }\n // -----------------------------------------------------------------------------\n // COLUMN\n // -----------------------------------------------------------------------------\n const COLUMN = {\n description: _lt(\"Column number of a specified cell.\"),\n args: args(`cell_reference (meta, default=${_lt(\"The cell in which the formula is entered\")}) ${_lt(\"The cell whose column number will be returned. Column A corresponds to 1.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (cellReference) {\n var _a;\n const _cellReference = cellReference || ((_a = this.__originCellXC) === null || _a === void 0 ? void 0 : _a.call(this));\n assert(() => !!_cellReference, \"In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter.\");\n const zone = toZone(_cellReference);\n return zone.left + 1;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // COLUMNS\n // -----------------------------------------------------------------------------\n const COLUMNS = {\n description: _lt(\"Number of columns in a specified array or range.\"),\n args: args(`range (meta) ${_lt(\"The range whose column count will be returned.\")}`),\n returns: [\"NUMBER\"],\n compute: function (range) {\n const zone = toZone(range);\n return zone.right - zone.left + 1;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // HLOOKUP\n // -----------------------------------------------------------------------------\n const HLOOKUP = {\n description: _lt(`Horizontal lookup`),\n args: args(`\n search_key (any) ${_lt(\"The value to search for. For example, 42, 'Cats', or I24.\")}\n range (range) ${_lt(\"The range to consider for the search. The first row in the range is searched for the key specified in search_key.\")}\n index (number) ${_lt(\"The row index of the value to be returned, where the first row in range is numbered 1.\")}\n is_sorted (boolean, default=${DEFAULT_IS_SORTED}) ${_lt(\"Indicates whether the row to be searched (the first row of the specified range) is sorted, in which case the closest match for search_key will be returned.\")}\n `),\n returns: [\"ANY\"],\n compute: function (searchKey, range, index, isSorted = DEFAULT_IS_SORTED) {\n const _index = Math.trunc(toNumber(index));\n const _searchKey = normalizeValue(searchKey);\n assert(() => 1 <= _index && _index <= range[0].length, _lt(\"[[FUNCTION_NAME]] evaluates to an out of bounds range.\"));\n const _isSorted = toBoolean(isSorted);\n let colIndex;\n if (_isSorted) {\n colIndex = dichotomicSearch(range, _searchKey, \"nextSmaller\", \"asc\", range.length, getNormalizedValueFromRowRange);\n }\n else {\n colIndex = linearSearch(range, _searchKey, \"strict\", range.length, getNormalizedValueFromRowRange);\n }\n const col = range[colIndex];\n assertAvailable(col, searchKey);\n return col[_index - 1];\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // LOOKUP\n // -----------------------------------------------------------------------------\n const LOOKUP = {\n description: _lt(`Look up a value.`),\n args: args(`\n search_key (any) ${_lt(\"The value to search for. For example, 42, 'Cats', or I24.\")}\n search_array (range) ${_lt(\"One method of using this function is to provide a single sorted row or column search_array to look through for the search_key with a second argument result_range. The other way is to combine these two arguments into one search_array where the first row or column is searched and a value is returned from the last row or column in the array. If search_key is not found, a non-exact match may be returned.\")}\n result_range (range, optional) ${_lt(\"The range from which to return a result. The value returned corresponds to the location where search_key is found in search_range. This range must be only a single row or column and should not be used if using the search_result_array method.\")}\n `),\n returns: [\"ANY\"],\n compute: function (searchKey, searchArray, resultRange) {\n let nbCol = searchArray.length;\n let nbRow = searchArray[0].length;\n const _searchKey = normalizeValue(searchKey);\n const verticalSearch = nbRow >= nbCol;\n const getElement = verticalSearch\n ? getNormalizedValueFromColumnRange\n : getNormalizedValueFromRowRange;\n const rangeLength = verticalSearch ? nbRow : nbCol;\n const index = dichotomicSearch(searchArray, _searchKey, \"nextSmaller\", \"asc\", rangeLength, getElement);\n assertAvailable(searchArray[0][index], searchKey);\n if (resultRange === undefined) {\n return (verticalSearch ? searchArray[nbCol - 1][index] : searchArray[index][nbRow - 1]);\n }\n nbCol = resultRange.length;\n nbRow = resultRange[0].length;\n assert(() => nbCol === 1 || nbRow === 1, _lt(\"The result_range must be a single row or a single column.\"));\n if (nbCol > 1) {\n assert(() => index <= nbCol - 1, _lt(\"[[FUNCTION_NAME]] evaluates to an out of range row value %s.\", (index + 1).toString()));\n return resultRange[index][0];\n }\n assert(() => index <= nbRow - 1, _lt(\"[[FUNCTION_NAME]] evaluates to an out of range column value %s.\", (index + 1).toString()));\n return resultRange[0][index];\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // MATCH\n // -----------------------------------------------------------------------------\n const DEFAULT_SEARCH_TYPE = 1;\n const MATCH = {\n description: _lt(`Position of item in range that matches value.`),\n args: args(`\n search_key (any) ${_lt(\"The value to search for. For example, 42, 'Cats', or I24.\")}\n range (any, range) ${_lt(\"The one-dimensional array to be searched.\")}\n search_type (number, default=${DEFAULT_SEARCH_TYPE}) ${_lt(\"The search method. 1 (default) finds the largest value less than or equal to search_key when range is sorted in ascending order. 0 finds the exact value when range is unsorted. -1 finds the smallest value greater than or equal to search_key when range is sorted in descending order.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (searchKey, range, searchType = DEFAULT_SEARCH_TYPE) {\n let _searchType = toNumber(searchType);\n const _searchKey = normalizeValue(searchKey);\n const nbCol = range.length;\n const nbRow = range[0].length;\n assert(() => nbCol === 1 || nbRow === 1, _lt(\"The range must be a single row or a single column.\"));\n let index = -1;\n const getElement = nbCol === 1 ? getNormalizedValueFromColumnRange : getNormalizedValueFromRowRange;\n const rangeLen = nbCol === 1 ? range[0].length : range.length;\n _searchType = Math.sign(_searchType);\n switch (_searchType) {\n case 1:\n index = dichotomicSearch(range, _searchKey, \"nextSmaller\", \"asc\", rangeLen, getElement);\n break;\n case 0:\n index = linearSearch(range, _searchKey, \"strict\", rangeLen, getElement);\n break;\n case -1:\n index = dichotomicSearch(range, _searchKey, \"nextGreater\", \"desc\", rangeLen, getElement);\n break;\n }\n assertAvailable(nbCol === 1 ? range[0][index] : range[index], searchKey);\n return index + 1;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ROW\n // -----------------------------------------------------------------------------\n const ROW = {\n description: _lt(\"Row number of a specified cell.\"),\n args: args(`cell_reference (meta, default=${_lt(\"The cell in which the formula is entered by default\")}) ${_lt(\"The cell whose row number will be returned.\")}`),\n returns: [\"NUMBER\"],\n compute: function (cellReference) {\n var _a;\n cellReference = cellReference || ((_a = this.__originCellXC) === null || _a === void 0 ? void 0 : _a.call(this));\n assert(() => !!cellReference, \"In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter.\");\n const zone = toZone(cellReference);\n return zone.top + 1;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // ROWS\n // -----------------------------------------------------------------------------\n const ROWS = {\n description: _lt(\"Number of rows in a specified array or range.\"),\n args: args(`range (meta) ${_lt(\"The range whose row count will be returned.\")}`),\n returns: [\"NUMBER\"],\n compute: function (range) {\n const zone = toZone(range);\n return zone.bottom - zone.top + 1;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // VLOOKUP\n // -----------------------------------------------------------------------------\n const VLOOKUP = {\n description: _lt(`Vertical lookup.`),\n args: args(`\n search_key (any) ${_lt(\"The value to search for. For example, 42, 'Cats', or I24.\")}\n range (any, range) ${_lt(\"The range to consider for the search. The first column in the range is searched for the key specified in search_key.\")}\n index (number) ${_lt(\"The column index of the value to be returned, where the first column in range is numbered 1.\")}\n is_sorted (boolean, default=${DEFAULT_IS_SORTED}) ${_lt(\"Indicates whether the column to be searched (the first column of the specified range) is sorted, in which case the closest match for search_key will be returned.\")}\n `),\n returns: [\"ANY\"],\n compute: function (searchKey, range, index, isSorted = DEFAULT_IS_SORTED) {\n const _index = Math.trunc(toNumber(index));\n const _searchKey = normalizeValue(searchKey);\n assert(() => 1 <= _index && _index <= range.length, _lt(\"[[FUNCTION_NAME]] evaluates to an out of bounds range.\"));\n const _isSorted = toBoolean(isSorted);\n let rowIndex;\n if (_isSorted) {\n rowIndex = dichotomicSearch(range, _searchKey, \"nextSmaller\", \"asc\", range[0].length, getNormalizedValueFromColumnRange);\n }\n else {\n rowIndex = linearSearch(range, _searchKey, \"strict\", range[0].length, getNormalizedValueFromColumnRange);\n }\n const value = range[_index - 1][rowIndex];\n assertAvailable(value, searchKey);\n return value;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // XLOOKUP\n // -----------------------------------------------------------------------------\n const XLOOKUP = {\n description: _lt(`Search a range for a match and return the corresponding item from a second range.`),\n args: args(`\n search_key (any) ${_lt(\"The value to search for.\")}\n lookup_range (any, range) ${_lt(\"The range to consider for the search. Should be a single column or a single row.\")}\n return_range (any, range) ${_lt(\"The range containing the return value. Should have the same dimensions as lookup_range.\")}\n if_not_found (any, lazy, optional) ${_lt(\"If a valid match is not found, return this value.\")}\n match_mode (any, default=${DEFAULT_MATCH_MODE}) ${_lt(\"(0) Exact match. (-1) Return next smaller item if no match. (1) Return next greater item if no match.\")}\n search_mode (any, default=${DEFAULT_SEARCH_MODE}) ${_lt(\"(1) Search starting at first item. \\\n (-1) Search starting at last item. \\\n (2) Perform a binary search that relies on lookup_array being sorted in ascending order. If not sorted, invalid results will be returned. \\\n (-2) Perform a binary search that relies on lookup_array being sorted in descending order. If not sorted, invalid results will be returned.\\\n \")}\n\n `),\n returns: [\"ANY\"],\n compute: function (searchKey, lookupRange, returnRange, defaultValue, matchMode = DEFAULT_MATCH_MODE, searchMode = DEFAULT_SEARCH_MODE) {\n const _matchMode = Math.trunc(toNumber(matchMode));\n const _searchMode = Math.trunc(toNumber(searchMode));\n const _searchKey = normalizeValue(searchKey);\n assert(() => lookupRange.length === 1 || lookupRange[0].length === 1, _lt(\"lookup_range should be either a single row or single column.\"));\n assert(() => returnRange.length === 1 || returnRange[0].length === 1, _lt(\"return_range should be either a single row or single column.\"));\n assert(() => returnRange.length === lookupRange.length &&\n returnRange[0].length === lookupRange[0].length, _lt(\"return_range should have the same dimensions as lookup_range.\"));\n assert(() => [-1, 1, -2, 2].includes(_searchMode), _lt(\"searchMode should be a value in [-1, 1, -2, 2].\"));\n assert(() => [-1, 0, 1].includes(_matchMode), _lt(\"matchMode should be a value in [-1, 0, 1].\"));\n const getElement = lookupRange.length === 1 ? getNormalizedValueFromColumnRange : getNormalizedValueFromRowRange;\n const rangeLen = lookupRange.length === 1 ? lookupRange[0].length : lookupRange.length;\n const mode = _matchMode === 0 ? \"strict\" : _matchMode === 1 ? \"nextGreater\" : \"nextSmaller\";\n const reverseSearch = _searchMode === -1;\n let index;\n if (_searchMode === 2 || _searchMode === -2) {\n const sortOrder = _searchMode === 2 ? \"asc\" : \"desc\";\n index = dichotomicSearch(lookupRange, _searchKey, mode, sortOrder, rangeLen, getElement);\n }\n else {\n index = linearSearch(lookupRange, _searchKey, mode, rangeLen, getElement, reverseSearch);\n }\n if (index !== -1) {\n return (lookupRange.length === 1 ? returnRange[0][index] : returnRange[index][0]);\n }\n const _defaultValue = defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue();\n assertAvailable(_defaultValue, searchKey);\n return _defaultValue;\n },\n isExported: true,\n };\n\n var lookup = /*#__PURE__*/Object.freeze({\n __proto__: null,\n COLUMN: COLUMN,\n COLUMNS: COLUMNS,\n HLOOKUP: HLOOKUP,\n LOOKUP: LOOKUP,\n MATCH: MATCH,\n ROW: ROW,\n ROWS: ROWS,\n VLOOKUP: VLOOKUP,\n XLOOKUP: XLOOKUP\n });\n\n // -----------------------------------------------------------------------------\n // ADD\n // -----------------------------------------------------------------------------\n const ADD = {\n description: _lt(`Sum of two numbers.`),\n args: args(`\n value1 (number) ${_lt(\"The first addend.\")}\n value2 (number) ${_lt(\"The second addend.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (value1, value2) => (value1 === null || value1 === void 0 ? void 0 : value1.format) || (value2 === null || value2 === void 0 ? void 0 : value2.format),\n compute: function (value1, value2) {\n return toNumber(value1) + toNumber(value2);\n },\n };\n // -----------------------------------------------------------------------------\n // CONCAT\n // -----------------------------------------------------------------------------\n const CONCAT = {\n description: _lt(`Concatenation of two values.`),\n args: args(`\n value1 (string) ${_lt(\"The value to which value2 will be appended.\")}\n value2 (string) ${_lt(\"The value to append to value1.\")}\n `),\n returns: [\"STRING\"],\n compute: function (value1, value2) {\n return toString(value1) + toString(value2);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // DIVIDE\n // -----------------------------------------------------------------------------\n const DIVIDE = {\n description: _lt(`One number divided by another.`),\n args: args(`\n dividend (number) ${_lt(\"The number to be divided.\")}\n divisor (number) ${_lt(\"The number to divide by.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (dividend, divisor) => (dividend === null || dividend === void 0 ? void 0 : dividend.format) || (divisor === null || divisor === void 0 ? void 0 : divisor.format),\n compute: function (dividend, divisor) {\n const _divisor = toNumber(divisor);\n assert(() => _divisor !== 0, _lt(\"The divisor must be different from zero.\"));\n return toNumber(dividend) / _divisor;\n },\n };\n // -----------------------------------------------------------------------------\n // EQ\n // -----------------------------------------------------------------------------\n function isEmpty(value) {\n return value === null || value === undefined;\n }\n const getNeutral = { number: 0, string: \"\", boolean: false };\n const EQ = {\n description: _lt(`Equal.`),\n args: args(`\n value1 (any) ${_lt(\"The first value.\")}\n value2 (any) ${_lt(\"The value to test against value1 for equality.\")}\n `),\n returns: [\"BOOLEAN\"],\n compute: function (value1, value2) {\n value1 = isEmpty(value1) ? getNeutral[typeof value2] : value1;\n value2 = isEmpty(value2) ? getNeutral[typeof value1] : value2;\n if (typeof value1 === \"string\") {\n value1 = value1.toUpperCase();\n }\n if (typeof value2 === \"string\") {\n value2 = value2.toUpperCase();\n }\n return value1 === value2;\n },\n };\n // -----------------------------------------------------------------------------\n // GT\n // -----------------------------------------------------------------------------\n function applyRelationalOperator(value1, value2, cb) {\n value1 = isEmpty(value1) ? getNeutral[typeof value2] : value1;\n value2 = isEmpty(value2) ? getNeutral[typeof value1] : value2;\n if (typeof value1 !== \"number\") {\n value1 = toString(value1).toUpperCase();\n }\n if (typeof value2 !== \"number\") {\n value2 = toString(value2).toUpperCase();\n }\n const tV1 = typeof value1;\n const tV2 = typeof value2;\n if (tV1 === \"string\" && tV2 === \"number\") {\n return true;\n }\n if (tV2 === \"string\" && tV1 === \"number\") {\n return false;\n }\n return cb(value1, value2);\n }\n const GT = {\n description: _lt(`Strictly greater than.`),\n args: args(`\n value1 (any) ${_lt(\"The value to test as being greater than value2.\")}\n value2 (any) ${_lt(\"The second value.\")}\n `),\n returns: [\"BOOLEAN\"],\n compute: function (value1, value2) {\n return applyRelationalOperator(value1, value2, (v1, v2) => {\n return v1 > v2;\n });\n },\n };\n // -----------------------------------------------------------------------------\n // GTE\n // -----------------------------------------------------------------------------\n const GTE = {\n description: _lt(`Greater than or equal to.`),\n args: args(`\n value1 (any) ${_lt(\"The value to test as being greater than or equal to value2.\")}\n value2 (any) ${_lt(\"The second value.\")}\n `),\n returns: [\"BOOLEAN\"],\n compute: function (value1, value2) {\n return applyRelationalOperator(value1, value2, (v1, v2) => {\n return v1 >= v2;\n });\n },\n };\n // -----------------------------------------------------------------------------\n // LT\n // -----------------------------------------------------------------------------\n const LT = {\n description: _lt(`Less than.`),\n args: args(`\n value1 (any) ${_lt(\"The value to test as being less than value2.\")}\n value2 (any) ${_lt(\"The second value.\")}\n `),\n returns: [\"BOOLEAN\"],\n compute: function (value1, value2) {\n return !GTE.compute(value1, value2);\n },\n };\n // -----------------------------------------------------------------------------\n // LTE\n // -----------------------------------------------------------------------------\n const LTE = {\n description: _lt(`Less than or equal to.`),\n args: args(`\n value1 (any) ${_lt(\"The value to test as being less than or equal to value2.\")}\n value2 (any) ${_lt(\"The second value.\")}\n `),\n returns: [\"BOOLEAN\"],\n compute: function (value1, value2) {\n return !GT.compute(value1, value2);\n },\n };\n // -----------------------------------------------------------------------------\n // MINUS\n // -----------------------------------------------------------------------------\n const MINUS = {\n description: _lt(`Difference of two numbers.`),\n args: args(`\n value1 (number) ${_lt(\"The minuend, or number to be subtracted from.\")}\n value2 (number) ${_lt(\"The subtrahend, or number to subtract from value1.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (value1, value2) => (value1 === null || value1 === void 0 ? void 0 : value1.format) || (value2 === null || value2 === void 0 ? void 0 : value2.format),\n compute: function (value1, value2) {\n return toNumber(value1) - toNumber(value2);\n },\n };\n // -----------------------------------------------------------------------------\n // MULTIPLY\n // -----------------------------------------------------------------------------\n const MULTIPLY = {\n description: _lt(`Product of two numbers`),\n args: args(`\n factor1 (number) ${_lt(\"The first multiplicand.\")}\n factor2 (number) ${_lt(\"The second multiplicand.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: (factor1, factor2) => (factor1 === null || factor1 === void 0 ? void 0 : factor1.format) || (factor2 === null || factor2 === void 0 ? void 0 : factor2.format),\n compute: function (factor1, factor2) {\n return toNumber(factor1) * toNumber(factor2);\n },\n };\n // -----------------------------------------------------------------------------\n // NE\n // -----------------------------------------------------------------------------\n const NE = {\n description: _lt(`Not equal.`),\n args: args(`\n value1 (any) ${_lt(\"The first value.\")}\n value2 (any) ${_lt(\"The value to test against value1 for inequality.\")}\n `),\n returns: [\"BOOLEAN\"],\n compute: function (value1, value2) {\n return !EQ.compute(value1, value2);\n },\n };\n // -----------------------------------------------------------------------------\n // POW\n // -----------------------------------------------------------------------------\n const POW = {\n description: _lt(`A number raised to a power.`),\n args: args(`\n base (number) ${_lt(\"The number to raise to the exponent power.\")}\n exponent (number) ${_lt(\"The exponent to raise base to.\")}\n `),\n returns: [\"BOOLEAN\"],\n compute: function (base, exponent) {\n return POWER.compute(base, exponent);\n },\n };\n // -----------------------------------------------------------------------------\n // UMINUS\n // -----------------------------------------------------------------------------\n const UMINUS = {\n description: _lt(`A number with the sign reversed.`),\n args: args(`\n value (number) ${_lt(\"The number to have its sign reversed. Equivalently, the number to multiply by -1.\")}\n `),\n computeFormat: (value) => value === null || value === void 0 ? void 0 : value.format,\n returns: [\"NUMBER\"],\n compute: function (value) {\n return -toNumber(value);\n },\n };\n // -----------------------------------------------------------------------------\n // UNARY_PERCENT\n // -----------------------------------------------------------------------------\n const UNARY_PERCENT = {\n description: _lt(`Value interpreted as a percentage.`),\n args: args(`\n percentage (number) ${_lt(\"The value to interpret as a percentage.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (percentage) {\n return toNumber(percentage) / 100;\n },\n };\n // -----------------------------------------------------------------------------\n // UPLUS\n // -----------------------------------------------------------------------------\n const UPLUS = {\n description: _lt(`A specified number, unchanged.`),\n args: args(`\n value (any) ${_lt(\"The number to return.\")}\n `),\n returns: [\"ANY\"],\n computeFormat: (value) => value === null || value === void 0 ? void 0 : value.format,\n compute: function (value) {\n return value === null ? \"\" : value;\n },\n };\n\n var operators = /*#__PURE__*/Object.freeze({\n __proto__: null,\n ADD: ADD,\n CONCAT: CONCAT,\n DIVIDE: DIVIDE,\n EQ: EQ,\n GT: GT,\n GTE: GTE,\n LT: LT,\n LTE: LTE,\n MINUS: MINUS,\n MULTIPLY: MULTIPLY,\n NE: NE,\n POW: POW,\n UMINUS: UMINUS,\n UNARY_PERCENT: UNARY_PERCENT,\n UPLUS: UPLUS\n });\n\n const DEFAULT_STARTING_AT = 1;\n /** Regex matching all the words in a string */\n const wordRegex = /[A-Za-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff]+/g;\n // -----------------------------------------------------------------------------\n // CHAR\n // -----------------------------------------------------------------------------\n const CHAR = {\n description: _lt(\"Gets character associated with number.\"),\n args: args(`\n table_number (number) ${_lt(\"The number of the character to look up from the current Unicode table in decimal format.\")}\n `),\n returns: [\"STRING\"],\n compute: function (tableNumber) {\n const _tableNumber = Math.trunc(toNumber(tableNumber));\n assert(() => _tableNumber >= 1, _lt(\"The table_number (%s) is out of range.\", _tableNumber.toString()));\n return String.fromCharCode(_tableNumber);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // CLEAN\n // -----------------------------------------------------------------------------\n const CLEAN = {\n description: _lt(\"Remove non-printable characters from a piece of text.\"),\n args: args(`\n text (string) ${_lt(\"The text whose non-printable characters are to be removed.\")}\n `),\n returns: [\"STRING\"],\n compute: function (text) {\n const _text = toString(text);\n let cleanedStr = \"\";\n for (const char of _text) {\n if (char && char.charCodeAt(0) > 31) {\n cleanedStr += char;\n }\n }\n return cleanedStr;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // CONCATENATE\n // -----------------------------------------------------------------------------\n const CONCATENATE = {\n description: _lt(\"Appends strings to one another.\"),\n args: args(`\n string1 (string, range) ${_lt(\"The initial string.\")}\n string2 (string, range, repeating) ${_lt(\"More strings to append in sequence.\")}\n `),\n returns: [\"STRING\"],\n compute: function (...values) {\n return reduceAny(values, (acc, a) => acc + toString(a), \"\");\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // EXACT\n // -----------------------------------------------------------------------------\n const EXACT = {\n description: _lt(\"Tests whether two strings are identical.\"),\n args: args(`\n string1 (string) ${_lt(\"The first string to compare.\")}\n string2 (string) ${_lt(\"The second string to compare.\")}\n `),\n returns: [\"BOOLEAN\"],\n compute: function (string1, string2) {\n return toString(string1) === toString(string2);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // FIND\n // -----------------------------------------------------------------------------\n const FIND = {\n description: _lt(\"First position of string found in text, case-sensitive.\"),\n args: args(`\n search_for (string) ${_lt(\"The string to look for within text_to_search.\")}\n text_to_search (string) ${_lt(\"The text to search for the first occurrence of search_for.\")}\n starting_at (number, default=${DEFAULT_STARTING_AT}) ${_lt(\"The character within text_to_search at which to start the search.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (searchFor, textToSearch, startingAt = DEFAULT_STARTING_AT) {\n const _searchFor = toString(searchFor);\n const _textToSearch = toString(textToSearch);\n const _startingAt = toNumber(startingAt);\n assert(() => _textToSearch !== \"\", _lt(`The text_to_search must be non-empty.`));\n assert(() => _startingAt >= 1, _lt(\"The starting_at (%s) must be greater than or equal to 1.\", _startingAt.toString()));\n const result = _textToSearch.indexOf(_searchFor, _startingAt - 1);\n assert(() => result >= 0, _lt(\"In [[FUNCTION_NAME]] evaluation, cannot find '%s' within '%s'.\", _searchFor.toString(), _textToSearch));\n return result + 1;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // JOIN\n // -----------------------------------------------------------------------------\n const JOIN = {\n description: _lt(\"Concatenates elements of arrays with delimiter.\"),\n args: args(`\n delimiter (string) ${_lt(\"The character or string to place between each concatenated value.\")}\n value_or_array1 (string, range) ${_lt(\"The value or values to be appended using delimiter.\")}\n value_or_array2 (string, range, repeating) ${_lt(\"More values to be appended using delimiter.\")}\n `),\n returns: [\"STRING\"],\n compute: function (delimiter, ...valuesOrArrays) {\n const _delimiter = toString(delimiter);\n return reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : \"\") + toString(a), \"\");\n },\n };\n // -----------------------------------------------------------------------------\n // LEFT\n // -----------------------------------------------------------------------------\n const LEFT = {\n description: _lt(\"Substring from beginning of specified string.\"),\n args: args(`\n text (string) ${_lt(\"The string from which the left portion will be returned.\")}\n number_of_characters (number, optional) ${_lt(\"The number of characters to return from the left side of string.\")}\n `),\n returns: [\"STRING\"],\n compute: function (text, ...args) {\n const _numberOfCharacters = args.length ? toNumber(args[0]) : 1;\n assert(() => _numberOfCharacters >= 0, _lt(\"The number_of_characters (%s) must be positive or null.\", _numberOfCharacters.toString()));\n return toString(text).substring(0, _numberOfCharacters);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // LEN\n // -----------------------------------------------------------------------------\n const LEN = {\n description: _lt(\"Length of a string.\"),\n args: args(`\n text (string) ${_lt(\"The string whose length will be returned.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (text) {\n return toString(text).length;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // LOWER\n // -----------------------------------------------------------------------------\n const LOWER = {\n description: _lt(\"Converts a specified string to lowercase.\"),\n args: args(`\n text (string) ${_lt(\"The string to convert to lowercase.\")}\n `),\n returns: [\"STRING\"],\n compute: function (text) {\n return toString(text).toLowerCase();\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // MID\n // -----------------------------------------------------------------------------\n const MID = {\n description: _lt(\"A segment of a string.\"),\n args: args(`\n text (string) ${_lt(\"The string to extract a segment from.\")}\n starting_at (number) ${_lt(\"The index from the left of string from which to begin extracting. The first character in string has the index 1.\")}\n extract_length (number) ${_lt(\"The length of the segment to extract.\")}\n `),\n returns: [\"STRING\"],\n compute: function (text, starting_at, extract_length) {\n const _text = toString(text);\n const _starting_at = toNumber(starting_at);\n const _extract_length = toNumber(extract_length);\n assert(() => _starting_at >= 1, _lt(\"The starting_at argument (%s) must be positive greater than one.\", _starting_at.toString()));\n assert(() => _extract_length >= 0, _lt(\"The extract_length argument (%s) must be positive or null.\", _extract_length.toString()));\n return _text.slice(_starting_at - 1, _starting_at + _extract_length - 1);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // PROPER\n // -----------------------------------------------------------------------------\n const PROPER = {\n description: _lt(\"Capitalizes each word in a specified string.\"),\n args: args(`\n text_to_capitalize (string) ${_lt(\"The text which will be returned with the first letter of each word in uppercase and all other letters in lowercase.\")}\n `),\n returns: [\"STRING\"],\n compute: function (text) {\n const _text = toString(text);\n return _text.replace(wordRegex, (word) => {\n return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();\n });\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // REPLACE\n // -----------------------------------------------------------------------------\n const REPLACE = {\n description: _lt(\"Replaces part of a text string with different text.\"),\n args: args(`\n text (string) ${_lt(\"The text, a part of which will be replaced.\")}\n position (number) ${_lt(\"The position where the replacement will begin (starting from 1).\")}\n length (number) ${_lt(\"The number of characters in the text to be replaced.\")}\n new_text (string) ${_lt(\"The text which will be inserted into the original text.\")}\n `),\n returns: [\"STRING\"],\n compute: function (text, position, length, newText) {\n const _position = toNumber(position);\n assert(() => _position >= 1, _lt(\"The position (%s) must be greater than or equal to 1.\", _position.toString()));\n const _text = toString(text);\n const _length = toNumber(length);\n const _newText = toString(newText);\n return _text.substring(0, _position - 1) + _newText + _text.substring(_position - 1 + _length);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // RIGHT\n // -----------------------------------------------------------------------------\n const RIGHT = {\n description: _lt(\"A substring from the end of a specified string.\"),\n args: args(`\n text (string) ${_lt(\"The string from which the right portion will be returned.\")}\n number_of_characters (number, optional) ${_lt(\"The number of characters to return from the right side of string.\")}\n `),\n returns: [\"STRING\"],\n compute: function (text, ...args) {\n const _numberOfCharacters = args.length ? toNumber(args[0]) : 1;\n assert(() => _numberOfCharacters >= 0, _lt(\"The number_of_characters (%s) must be positive or null.\", _numberOfCharacters.toString()));\n const _text = toString(text);\n const stringLength = _text.length;\n return _text.substring(stringLength - _numberOfCharacters, stringLength);\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // SEARCH\n // -----------------------------------------------------------------------------\n const SEARCH = {\n description: _lt(\"First position of string found in text, ignoring case.\"),\n args: args(`\n search_for (string) ${_lt(\"The string to look for within text_to_search.\")}\n text_to_search (string) ${_lt(\"The text to search for the first occurrence of search_for.\")}\n starting_at (number, default=${DEFAULT_STARTING_AT}) ${_lt(\"The character within text_to_search at which to start the search.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (searchFor, textToSearch, startingAt = DEFAULT_STARTING_AT) {\n const _searchFor = toString(searchFor).toLowerCase();\n const _textToSearch = toString(textToSearch).toLowerCase();\n const _startingAt = toNumber(startingAt);\n assert(() => _textToSearch !== \"\", _lt(`The text_to_search must be non-empty.`));\n assert(() => _startingAt >= 1, _lt(\"The starting_at (%s) must be greater than or equal to 1.\", _startingAt.toString()));\n const result = _textToSearch.indexOf(_searchFor, _startingAt - 1);\n assert(() => result >= 0, _lt(\"In [[FUNCTION_NAME]] evaluation, cannot find '%s' within '%s'.\", _searchFor, _textToSearch));\n return result + 1;\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // SUBSTITUTE\n // -----------------------------------------------------------------------------\n const SUBSTITUTE = {\n description: _lt(\"Replaces existing text with new text in a string.\"),\n args: args(`\n text_to_search (string) ${_lt(\"The text within which to search and replace.\")}\n search_for (string) ${_lt(\"The string to search for within text_to_search.\")}\n replace_with (string) ${_lt(\"The string that will replace search_for.\")}\n occurrence_number (number, optional) ${_lt(\"The instance of search_for within text_to_search to replace with replace_with. By default, all occurrences of search_for are replaced; however, if occurrence_number is specified, only the indicated instance of search_for is replaced.\")}\n `),\n returns: [\"NUMBER\"],\n compute: function (textToSearch, searchFor, replaceWith, occurrenceNumber) {\n const _occurrenceNumber = toNumber(occurrenceNumber);\n assert(() => _occurrenceNumber >= 0, _lt(\"The occurrenceNumber (%s) must be positive or null.\", _occurrenceNumber.toString()));\n const _textToSearch = toString(textToSearch);\n const _searchFor = toString(searchFor);\n if (_searchFor === \"\") {\n return _textToSearch;\n }\n const _replaceWith = toString(replaceWith);\n const reg = new RegExp(escapeRegExp(_searchFor), \"g\");\n if (_occurrenceNumber === 0) {\n return _textToSearch.replace(reg, _replaceWith);\n }\n let n = 0;\n return _textToSearch.replace(reg, (text) => (++n === _occurrenceNumber ? _replaceWith : text));\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // TEXTJOIN\n // -----------------------------------------------------------------------------\n const TEXTJOIN = {\n description: _lt(\"Combines text from multiple strings and/or arrays.\"),\n args: args(`\n delimiter (string) ${_lt(\" A string, possible empty, or a reference to a valid string. If empty, the text will be simply concatenated.\")}\n ignore_empty (boolean) ${_lt(\"A boolean; if TRUE, empty cells selected in the text arguments won't be included in the result.\")}\n text1 (string, range) ${_lt(\"Any text item. This could be a string, or an array of strings in a range.\")}\n text2 (string, range, repeating) ${_lt(\"Additional text item(s).\")}\n `),\n returns: [\"STRING\"],\n compute: function (delimiter, ignoreEmpty, ...textsOrArrays) {\n const _delimiter = toString(delimiter);\n const _ignoreEmpty = toBoolean(ignoreEmpty);\n let n = 0;\n return reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty && toString(a) === \"\") ? (n++ ? acc + _delimiter : \"\") + toString(a) : acc, \"\");\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // TRIM\n // -----------------------------------------------------------------------------\n const TRIM = {\n description: _lt(\"Removes space characters.\"),\n args: args(`\n text (string) ${_lt(\"The text or reference to a cell containing text to be trimmed.\")}\n `),\n returns: [\"STRING\"],\n compute: function (text) {\n return toString(text).trim();\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // UPPER\n // -----------------------------------------------------------------------------\n const UPPER = {\n description: _lt(\"Converts a specified string to uppercase.\"),\n args: args(`\n text (string) ${_lt(\"The string to convert to uppercase.\")}\n `),\n returns: [\"STRING\"],\n compute: function (text) {\n return toString(text).toUpperCase();\n },\n isExported: true,\n };\n // -----------------------------------------------------------------------------\n // TEXT\n // -----------------------------------------------------------------------------\n const TEXT = {\n description: _lt(\"Converts a number to text according to a specified format.\"),\n args: args(`\n number (number) ${_lt(\"The number, date or time to format.\")}\n format (string) ${_lt(\"The pattern by which to format the number, enclosed in quotation marks.\")}\n `),\n returns: [\"STRING\"],\n compute: function (number, format) {\n const _number = toNumber(number);\n return formatValue(_number, toString(format));\n },\n isExported: true,\n };\n\n var text = /*#__PURE__*/Object.freeze({\n __proto__: null,\n CHAR: CHAR,\n CLEAN: CLEAN,\n CONCATENATE: CONCATENATE,\n EXACT: EXACT,\n FIND: FIND,\n JOIN: JOIN,\n LEFT: LEFT,\n LEN: LEN,\n LOWER: LOWER,\n MID: MID,\n PROPER: PROPER,\n REPLACE: REPLACE,\n RIGHT: RIGHT,\n SEARCH: SEARCH,\n SUBSTITUTE: SUBSTITUTE,\n TEXTJOIN: TEXTJOIN,\n TRIM: TRIM,\n UPPER: UPPER,\n TEXT: TEXT\n });\n\n // -----------------------------------------------------------------------------\n // HYPERLINK\n // -----------------------------------------------------------------------------\n const HYPERLINK = {\n description: _lt(\"Creates a hyperlink in a cell.\"),\n args: args(`\n url (string) ${_lt(\"The full URL of the link enclosed in quotation marks.\")}\n link_label (string, optional) ${_lt(\"The text to display in the cell, enclosed in quotation marks.\")}\n `),\n returns: [\"STRING\"],\n compute: function (url, linkLabel) {\n const processedUrl = toString(url).trim();\n const processedLabel = toString(linkLabel) || processedUrl;\n if (processedUrl === \"\")\n return processedLabel;\n return markdownLink(processedLabel, processedUrl);\n },\n isExported: true,\n };\n\n var web = /*#__PURE__*/Object.freeze({\n __proto__: null,\n HYPERLINK: HYPERLINK\n });\n\n const functions$3 = {\n database,\n date,\n financial,\n info,\n lookup,\n logical,\n math,\n misc: misc$1,\n operators,\n statistical,\n text,\n engineering,\n web,\n };\n const functionNameRegex = /^[A-Z0-9\\_\\.]+$/;\n //------------------------------------------------------------------------------\n // Function registry\n //------------------------------------------------------------------------------\n class FunctionRegistry extends Registry {\n constructor() {\n super(...arguments);\n this.mapping = {};\n }\n add(name, addDescr) {\n name = name.toUpperCase();\n if (!name.match(functionNameRegex)) {\n throw new Error(_lt(\"Invalid function name %s. Function names can exclusively contain alphanumerical values separated by dots (.) or underscore (_)\", name));\n }\n const descr = addMetaInfoFromArg(addDescr);\n validateArguments(descr.args);\n function computeValueAndFormat(...args) {\n const computeValue = descr.compute.bind(this);\n const computeFormat = descr.computeFormat ? descr.computeFormat.bind(this) : () => undefined;\n return {\n value: computeValue(...extractArgValuesFromArgs(args)),\n format: computeFormat(...args),\n };\n }\n this.mapping[name] = computeValueAndFormat;\n super.add(name, descr);\n return this;\n }\n }\n function extractArgValuesFromArgs(args) {\n return args.map((arg) => {\n if (arg === undefined) {\n return undefined;\n }\n if (typeof arg === \"function\") {\n return () => _extractArgValuesFromArgs(arg());\n }\n return _extractArgValuesFromArgs(arg);\n });\n }\n function _extractArgValuesFromArgs(arg) {\n if (Array.isArray(arg)) {\n return arg.map((col) => col.map((simpleArg) => simpleArg === null || simpleArg === void 0 ? void 0 : simpleArg.value));\n }\n return arg === null || arg === void 0 ? void 0 : arg.value;\n }\n const functionRegistry = new FunctionRegistry();\n for (let category in functions$3) {\n const fns = functions$3[category];\n for (let name in fns) {\n const addDescr = fns[name];\n addDescr.category = category;\n name = name.replace(/_/g, \".\");\n functionRegistry.add(name, { isExported: false, ...addDescr });\n }\n }\n\n /**\n * Tokenizer\n *\n * A tokenizer is a piece of code whose job is to transform a string into a list\n * of \"tokens\". For example, \"(12+\" is converted into:\n * [{type: \"LEFT_PAREN\", value: \"(\"},\n * {type: \"NUMBER\", value: \"12\"},\n * {type: \"OPERATOR\", value: \"+\"}]\n *\n * As the example shows, a tokenizer does not care about the meaning behind those\n * tokens. It only cares about the structure.\n *\n * The tokenizer is usually the first step in a compilation pipeline. Also, it\n * is useful for the composer, which needs to be able to work with incomplete\n * formulas.\n */\n const functions$2 = functionRegistry.content;\n const POSTFIX_UNARY_OPERATORS = [\"%\"];\n const OPERATORS = \"+,-,*,/,:,=,<>,>=,>,<=,<,^,&\".split(\",\").concat(POSTFIX_UNARY_OPERATORS);\n function tokenize(str) {\n const chars = str.split(\"\");\n const result = [];\n while (chars.length) {\n let token = tokenizeSpace(chars) ||\n tokenizeMisc(chars) ||\n tokenizeOperator(chars) ||\n tokenizeString(chars) ||\n tokenizeDebugger(chars) ||\n tokenizeInvalidRange(chars) ||\n tokenizeNumber(chars) ||\n tokenizeSymbol(chars);\n if (!token) {\n token = { type: \"UNKNOWN\", value: chars.shift() };\n }\n result.push(token);\n }\n return result;\n }\n function tokenizeDebugger(chars) {\n if (chars[0] === \"?\") {\n chars.shift();\n return { type: \"DEBUGGER\", value: \"?\" };\n }\n return null;\n }\n const misc = {\n \",\": \"COMMA\",\n \"(\": \"LEFT_PAREN\",\n \")\": \"RIGHT_PAREN\",\n };\n function tokenizeMisc(chars) {\n if (chars[0] in misc) {\n const value = chars.shift();\n const type = misc[value];\n return { type, value };\n }\n return null;\n }\n function startsWith(chars, op) {\n for (let i = 0; i < op.length; i++) {\n if (op[i] !== chars[i]) {\n return false;\n }\n }\n return true;\n }\n function tokenizeOperator(chars) {\n for (let op of OPERATORS) {\n if (startsWith(chars, op)) {\n chars.splice(0, op.length);\n return { type: \"OPERATOR\", value: op };\n }\n }\n return null;\n }\n function tokenizeNumber(chars) {\n const match = concat(chars).match(formulaNumberRegexp);\n if (match) {\n chars.splice(0, match[0].length);\n return { type: \"NUMBER\", value: match[0] };\n }\n return null;\n }\n function tokenizeString(chars) {\n if (chars[0] === '\"') {\n const startChar = chars.shift();\n let letters = startChar;\n while (chars[0] && (chars[0] !== startChar || letters[letters.length - 1] === \"\\\\\")) {\n letters += chars.shift();\n }\n if (chars[0] === '\"') {\n letters += chars.shift();\n }\n return {\n type: \"STRING\",\n value: letters,\n };\n }\n return null;\n }\n const separatorRegexp = /\\w|\\.|!|\\$/;\n /**\n * A \"Symbol\" is just basically any word-like element that can appear in a\n * formula, which is not a string. So:\n * A1\n * SUM\n * CEILING.MATH\n * A$1\n * Sheet2!A2\n * 'Sheet 2'!A2\n *\n * are examples of symbols\n */\n function tokenizeSymbol(chars) {\n let result = \"\";\n // there are two main cases to manage: either something which starts with\n // a ', like 'Sheet 2'A2, or a word-like element.\n if (chars[0] === \"'\") {\n let lastChar = chars.shift();\n result += lastChar;\n while (chars[0]) {\n lastChar = chars.shift();\n result += lastChar;\n if (lastChar === \"'\") {\n if (chars[0] && chars[0] === \"'\") {\n lastChar = chars.shift();\n result += lastChar;\n }\n else {\n break;\n }\n }\n }\n if (lastChar !== \"'\") {\n return {\n type: \"UNKNOWN\",\n value: result,\n };\n }\n }\n while (chars[0] && chars[0].match(separatorRegexp)) {\n result += chars.shift();\n }\n if (result.length) {\n const value = result;\n const isFunction = value.toUpperCase() in functions$2;\n if (isFunction) {\n return { type: \"FUNCTION\", value };\n }\n const isReference = value.match(rangeReference);\n if (isReference) {\n return { type: \"REFERENCE\", value };\n }\n else {\n return { type: \"SYMBOL\", value };\n }\n }\n return null;\n }\n const whiteSpaceRegexp = /\\s/;\n function tokenizeSpace(chars) {\n let length = 0;\n while (chars[0] && chars[0].match(whiteSpaceRegexp)) {\n length++;\n chars.shift();\n }\n if (length) {\n return { type: \"SPACE\", value: \" \".repeat(length) };\n }\n return null;\n }\n function tokenizeInvalidRange(chars) {\n if (startsWith(chars, INCORRECT_RANGE_STRING)) {\n chars.splice(0, INCORRECT_RANGE_STRING.length);\n return { type: \"INVALID_REFERENCE\", value: INCORRECT_RANGE_STRING };\n }\n return null;\n }\n\n const functionRegex = /[a-zA-Z0-9\\_]+(\\.[a-zA-Z0-9\\_]+)*/;\n const UNARY_OPERATORS_PREFIX = [\"-\", \"+\"];\n const UNARY_OPERATORS_POSTFIX = [\"%\"];\n const ASSOCIATIVE_OPERATORS = [\"*\", \"+\", \"&\"];\n const OP_PRIORITY = {\n \"^\": 30,\n \"%\": 30,\n \"*\": 20,\n \"/\": 20,\n \"+\": 15,\n \"-\": 15,\n \"&\": 13,\n \">\": 10,\n \"<>\": 10,\n \">=\": 10,\n \"<\": 10,\n \"<=\": 10,\n \"=\": 10,\n };\n const FUNCTION_BP = 6;\n function bindingPower(token) {\n switch (token.type) {\n case \"NUMBER\":\n case \"SYMBOL\":\n case \"REFERENCE\":\n return 0;\n case \"COMMA\":\n return 3;\n case \"LEFT_PAREN\":\n return 5;\n case \"RIGHT_PAREN\":\n return 5;\n case \"OPERATOR\":\n return OP_PRIORITY[token.value] || 15;\n }\n throw new BadExpressionError(_lt(\"Unknown token: %s\", token.value));\n }\n function parsePrefix(current, tokens) {\n var _a, _b, _c, _d;\n switch (current.type) {\n case \"DEBUGGER\":\n const next = parseExpression(tokens, 1000);\n next.debug = true;\n return next;\n case \"NUMBER\":\n return { type: \"NUMBER\", value: parseNumber(current.value) };\n case \"STRING\":\n return { type: \"STRING\", value: removeStringQuotes(current.value) };\n case \"FUNCTION\":\n if (tokens.shift().type !== \"LEFT_PAREN\") {\n throw new BadExpressionError(_lt(\"Wrong function call\"));\n }\n else {\n const args = [];\n if (tokens[0] && tokens[0].type !== \"RIGHT_PAREN\") {\n if (tokens[0].type === \"COMMA\") {\n args.push({ type: \"UNKNOWN\", value: \"\" });\n }\n else {\n args.push(parseExpression(tokens, FUNCTION_BP));\n }\n while (((_a = tokens[0]) === null || _a === void 0 ? void 0 : _a.type) === \"COMMA\") {\n tokens.shift();\n const token = tokens[0];\n if ((token === null || token === void 0 ? void 0 : token.type) === \"RIGHT_PAREN\") {\n args.push({ type: \"UNKNOWN\", value: \"\" });\n break;\n }\n else if ((token === null || token === void 0 ? void 0 : token.type) === \"COMMA\") {\n args.push({ type: \"UNKNOWN\", value: \"\" });\n }\n else {\n args.push(parseExpression(tokens, FUNCTION_BP));\n }\n }\n }\n const closingToken = tokens.shift();\n if (!closingToken || closingToken.type !== \"RIGHT_PAREN\") {\n throw new BadExpressionError(_lt(\"Wrong function call\"));\n }\n return { type: \"FUNCALL\", value: current.value, args };\n }\n case \"INVALID_REFERENCE\":\n throw new InvalidReferenceError();\n case \"REFERENCE\":\n if (((_b = tokens[0]) === null || _b === void 0 ? void 0 : _b.value) === \":\" && ((_c = tokens[1]) === null || _c === void 0 ? void 0 : _c.type) === \"REFERENCE\") {\n tokens.shift();\n const rightReference = tokens.shift();\n return {\n type: \"REFERENCE\",\n value: `${current.value}:${rightReference === null || rightReference === void 0 ? void 0 : rightReference.value}`,\n };\n }\n return {\n type: \"REFERENCE\",\n value: current.value,\n };\n case \"SYMBOL\":\n if ([\"TRUE\", \"FALSE\"].includes(current.value.toUpperCase())) {\n return { type: \"BOOLEAN\", value: current.value.toUpperCase() === \"TRUE\" };\n }\n else {\n if (current.value) {\n if (functionRegex.test(current.value) && ((_d = tokens[0]) === null || _d === void 0 ? void 0 : _d.type) === \"LEFT_PAREN\") {\n throw new UnknownFunctionError(current.value);\n }\n throw new BadExpressionError(_lt(\"Invalid formula\"));\n }\n return { type: \"STRING\", value: current.value };\n }\n case \"LEFT_PAREN\":\n const result = parseExpression(tokens, 5);\n if (!tokens.length || tokens[0].type !== \"RIGHT_PAREN\") {\n throw new BadExpressionError(_lt(\"Unmatched left parenthesis\"));\n }\n tokens.shift();\n return result;\n default:\n if (current.type === \"OPERATOR\" && UNARY_OPERATORS_PREFIX.includes(current.value)) {\n return {\n type: \"UNARY_OPERATION\",\n value: current.value,\n operand: parseExpression(tokens, OP_PRIORITY[current.value]),\n };\n }\n throw new BadExpressionError(_lt(\"Unexpected token: %s\", current.value));\n }\n }\n function parseInfix(left, current, tokens) {\n if (current.type === \"OPERATOR\") {\n const bp = bindingPower(current);\n if (UNARY_OPERATORS_POSTFIX.includes(current.value)) {\n return {\n type: \"UNARY_OPERATION\",\n value: current.value,\n operand: left,\n postfix: true,\n };\n }\n else {\n const right = parseExpression(tokens, bp);\n return {\n type: \"BIN_OPERATION\",\n value: current.value,\n left,\n right,\n };\n }\n }\n throw new BadExpressionError(DEFAULT_ERROR_MESSAGE);\n }\n function parseExpression(tokens, bp) {\n const token = tokens.shift();\n if (!token) {\n throw new BadExpressionError(DEFAULT_ERROR_MESSAGE);\n }\n let expr = parsePrefix(token, tokens);\n while (tokens[0] && bindingPower(tokens[0]) > bp) {\n expr = parseInfix(expr, tokens.shift(), tokens);\n }\n return expr;\n }\n /**\n * Parse an expression (as a string) into an AST.\n */\n function parse(str) {\n return parseTokens(tokenize(str));\n }\n function parseTokens(tokens) {\n tokens = tokens.filter((x) => x.type !== \"SPACE\");\n if (tokens[0].type === \"OPERATOR\" && tokens[0].value === \"=\") {\n tokens.splice(0, 1);\n }\n const result = parseExpression(tokens, 0);\n if (tokens.length) {\n throw new BadExpressionError(DEFAULT_ERROR_MESSAGE);\n }\n return result;\n }\n /**\n * Allows to visit all nodes of an AST and apply a mapping function\n * to nodes of a specific type.\n * Useful if you want to convert some part of a formula.\n *\n * e.g.\n * ```ts\n * convertAstNodes(ast, \"FUNCALL\", convertFormulaToExcel)\n *\n * function convertFormulaToExcel(ast: ASTFuncall) {\n * // ...\n * return modifiedAst\n * }\n * ```\n */\n function convertAstNodes(ast, type, fn) {\n if (type === ast.type) {\n ast = fn(ast);\n }\n switch (ast.type) {\n case \"FUNCALL\":\n return {\n ...ast,\n args: ast.args.map((child) => convertAstNodes(child, type, fn)),\n };\n case \"UNARY_OPERATION\":\n return {\n ...ast,\n operand: convertAstNodes(ast.operand, type, fn),\n };\n case \"BIN_OPERATION\":\n return {\n ...ast,\n right: convertAstNodes(ast.right, type, fn),\n left: convertAstNodes(ast.left, type, fn),\n };\n default:\n return ast;\n }\n }\n /**\n * Converts an ast formula to the corresponding string\n */\n function astToFormula(ast) {\n switch (ast.type) {\n case \"FUNCALL\":\n const args = ast.args.map((arg) => astToFormula(arg));\n return `${ast.value}(${args.join(\",\")})`;\n case \"NUMBER\":\n return ast.value.toString();\n case \"REFERENCE\":\n return ast.value;\n case \"STRING\":\n return `\"${ast.value}\"`;\n case \"BOOLEAN\":\n return ast.value ? \"TRUE\" : \"FALSE\";\n case \"UNARY_OPERATION\":\n return ast.postfix\n ? leftOperandToFormula(ast) + ast.value\n : ast.value + rightOperandToFormula(ast);\n case \"BIN_OPERATION\":\n return leftOperandToFormula(ast) + ast.value + rightOperandToFormula(ast);\n default:\n return ast.value;\n }\n }\n /**\n * Convert the left operand of a binary operation to the corresponding string\n * and enclose the result inside parenthesis if necessary.\n */\n function leftOperandToFormula(operationAST) {\n const mainOperator = operationAST.value;\n const leftOperation = \"left\" in operationAST ? operationAST.left : operationAST.operand;\n const leftOperator = leftOperation.value;\n const needParenthesis = leftOperation.type === \"BIN_OPERATION\" && OP_PRIORITY[leftOperator] < OP_PRIORITY[mainOperator];\n return needParenthesis ? `(${astToFormula(leftOperation)})` : astToFormula(leftOperation);\n }\n /**\n * Convert the right operand of a binary or unary operation to the corresponding string\n * and enclose the result inside parenthesis if necessary.\n */\n function rightOperandToFormula(operationAST) {\n const mainOperator = operationAST.value;\n const rightOperation = \"right\" in operationAST ? operationAST.right : operationAST.operand;\n const rightPriority = OP_PRIORITY[rightOperation.value];\n const mainPriority = OP_PRIORITY[mainOperator];\n let needParenthesis = false;\n if (rightOperation.type !== \"BIN_OPERATION\") {\n needParenthesis = false;\n }\n else if (rightPriority < mainPriority) {\n needParenthesis = true;\n }\n else if (rightPriority === mainPriority && !ASSOCIATIVE_OPERATORS.includes(mainOperator)) {\n needParenthesis = true;\n }\n return needParenthesis ? `(${astToFormula(rightOperation)})` : astToFormula(rightOperation);\n }\n\n var State;\n (function (State) {\n /**\n * Initial state.\n * Expecting any reference for the left part of a range\n * e.g. \"A1\", \"1\", \"A\", \"Sheet1!A1\", \"Sheet1!A\"\n */\n State[State[\"LeftRef\"] = 0] = \"LeftRef\";\n /**\n * Expecting any reference for the right part of a range\n * e.g. \"A1\", \"1\", \"A\", \"Sheet1!A1\", \"Sheet1!A\"\n */\n State[State[\"RightRef\"] = 1] = \"RightRef\";\n /**\n * Expecting the separator without any constraint on the right part\n */\n State[State[\"Separator\"] = 2] = \"Separator\";\n /**\n * Expecting the separator for a full column range\n */\n State[State[\"FullColumnSeparator\"] = 3] = \"FullColumnSeparator\";\n /**\n * Expecting the separator for a full row range\n */\n State[State[\"FullRowSeparator\"] = 4] = \"FullRowSeparator\";\n /**\n * Expecting the right part of a full column range\n * e.g. \"1\", \"A1\"\n */\n State[State[\"RightColumnRef\"] = 5] = \"RightColumnRef\";\n /**\n * Expecting the right part of a full row range\n * e.g. \"A\", \"A1\"\n */\n State[State[\"RightRowRef\"] = 6] = \"RightRowRef\";\n /**\n * Final state. A range has been matched\n */\n State[State[\"Found\"] = 7] = \"Found\";\n })(State || (State = {}));\n const goTo = (state, guard = () => true) => [\n {\n goTo: state,\n guard,\n },\n ];\n const goToMulti = (state, guard = () => true) => ({\n goTo: state,\n guard,\n });\n const machine = {\n [State.LeftRef]: {\n REFERENCE: goTo(State.Separator),\n NUMBER: goTo(State.FullRowSeparator),\n SYMBOL: [\n goToMulti(State.FullColumnSeparator, (token) => isColReference(token.value)),\n goToMulti(State.FullRowSeparator, (token) => isRowReference(token.value)),\n ],\n },\n [State.FullColumnSeparator]: {\n SPACE: goTo(State.FullColumnSeparator),\n OPERATOR: goTo(State.RightColumnRef, (token) => token.value === \":\"),\n },\n [State.FullRowSeparator]: {\n SPACE: goTo(State.FullRowSeparator),\n OPERATOR: goTo(State.RightRowRef, (token) => token.value === \":\"),\n },\n [State.Separator]: {\n SPACE: goTo(State.Separator),\n OPERATOR: goTo(State.RightRef, (token) => token.value === \":\"),\n },\n [State.RightRef]: {\n SPACE: goTo(State.RightRef),\n NUMBER: goTo(State.Found),\n REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),\n SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),\n },\n [State.RightColumnRef]: {\n SPACE: goTo(State.RightColumnRef),\n SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),\n REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),\n },\n [State.RightRowRef]: {\n SPACE: goTo(State.RightRowRef),\n NUMBER: goTo(State.Found),\n REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),\n },\n [State.Found]: {},\n };\n /**\n * Check if the list of tokens starts with a sequence of tokens representing\n * a range.\n * If a range is found, the sequence is removed from the list and is returned\n * as a single token.\n */\n function matchReference(tokens) {\n var _a;\n let head = 0;\n let transitions = machine[State.LeftRef];\n const matchedTokens = [];\n while (transitions !== undefined) {\n const token = tokens[head++];\n if (!token) {\n return null;\n }\n const transition = (_a = transitions[token.type]) === null || _a === void 0 ? void 0 : _a.find((transition) => transition.guard(token));\n const nextState = transition ? transition.goTo : undefined;\n switch (nextState) {\n case undefined:\n return null;\n case State.Found:\n matchedTokens.push(token);\n tokens.splice(0, head);\n return {\n type: \"REFERENCE\",\n value: concat(matchedTokens.map((token) => token.value)),\n };\n default:\n transitions = machine[nextState];\n matchedTokens.push(token);\n break;\n }\n }\n return null;\n }\n /**\n * Take the result of the tokenizer and transform it to be usable in the\n * manipulations of range\n *\n * @param formula\n */\n function rangeTokenize(formula) {\n const tokens = tokenize(formula);\n const result = [];\n while (tokens.length) {\n result.push(matchReference(tokens) || tokens.shift());\n }\n return result;\n }\n\n const functions$1 = functionRegistry.content;\n const OPERATOR_MAP = {\n \"=\": \"EQ\",\n \"+\": \"ADD\",\n \"-\": \"MINUS\",\n \"*\": \"MULTIPLY\",\n \"/\": \"DIVIDE\",\n \">=\": \"GTE\",\n \"<>\": \"NE\",\n \">\": \"GT\",\n \"<=\": \"LTE\",\n \"<\": \"LT\",\n \"^\": \"POWER\",\n \"&\": \"CONCATENATE\",\n };\n const UNARY_OPERATOR_MAP = {\n \"-\": \"UMINUS\",\n \"+\": \"UPLUS\",\n \"%\": \"UNARY.PERCENT\",\n };\n /**\n * Takes a list of strings that might be single or multiline\n * and maps them in a list of single line strings.\n */\n function splitCodeLines(codeBlocks) {\n return codeBlocks\n .join(\"\\n\")\n .split(\"\\n\")\n .filter((line) => line.trim() !== \"\");\n }\n // this cache contains all compiled function code, grouped by \"structure\". For\n // example, \"=2*sum(A1:A4)\" and \"=2*sum(B1:B4)\" are compiled into the same\n // structural function.\n // It is only exported for testing purposes\n const functionCache = {};\n // -----------------------------------------------------------------------------\n // COMPILER\n // -----------------------------------------------------------------------------\n function compile(formula) {\n const tokens = rangeTokenize(formula);\n const { dependencies, constantValues } = formulaArguments(tokens);\n const cacheKey = compilationCacheKey(tokens, dependencies, constantValues);\n if (!functionCache[cacheKey]) {\n const ast = parseTokens([...tokens]);\n let nextId = 1;\n if (ast.type === \"BIN_OPERATION\" && ast.value === \":\") {\n throw new BadExpressionError(_lt(\"Invalid formula\"));\n }\n if (ast.type === \"UNKNOWN\") {\n throw new BadExpressionError(_lt(\"Invalid formula\"));\n }\n const compiledAST = compileAST(ast);\n const code = splitCodeLines([\n `// ${cacheKey}`,\n compiledAST.code,\n `return ${compiledAST.id};`,\n ]).join(\"\\n\");\n let baseFunction = new Function(\"deps\", // the dependencies in the current formula\n \"ref\", // a function to access a certain dependency at a given index\n \"range\", // same as above, but guarantee that the result is in the form of a range\n \"ctx\", code);\n functionCache[cacheKey] = {\n // @ts-ignore\n execute: baseFunction,\n };\n /**\n * This function compile the function arguments. It is mostly straightforward,\n * except that there is a non trivial transformation in one situation:\n *\n * If a function argument is asking for a range, and get a cell, we transform\n * the cell value into a range. This allow the grid model to differentiate\n * between a cell value and a non cell value.\n */\n function compileFunctionArgs(ast) {\n const functionDefinition = functions$1[ast.value.toUpperCase()];\n const currentFunctionArguments = ast.args;\n // check if arguments are supplied in the correct quantities\n const nbrArg = currentFunctionArguments.length;\n if (nbrArg < functionDefinition.minArgRequired) {\n throw new BadExpressionError(_lt(\"Invalid number of arguments for the %s function. Expected %s minimum, but got %s instead.\", ast.value.toUpperCase(), functionDefinition.minArgRequired.toString(), nbrArg.toString()));\n }\n if (nbrArg > functionDefinition.maxArgPossible) {\n throw new BadExpressionError(_lt(\"Invalid number of arguments for the %s function. Expected %s maximum, but got %s instead.\", ast.value.toUpperCase(), functionDefinition.maxArgPossible.toString(), nbrArg.toString()));\n }\n const repeatingArg = functionDefinition.nbrArgRepeating;\n if (repeatingArg > 1) {\n const argBeforeRepeat = functionDefinition.args.length - repeatingArg;\n const nbrRepeatingArg = nbrArg - argBeforeRepeat;\n if (nbrRepeatingArg % repeatingArg !== 0) {\n throw new BadExpressionError(_lt(\"Invalid number of arguments for the %s function. Expected all arguments after position %s to be supplied by groups of %s arguments\", ast.value.toUpperCase(), argBeforeRepeat.toString(), repeatingArg.toString()));\n }\n }\n let listArgs = [];\n for (let i = 0; i < nbrArg; i++) {\n const argPosition = functionDefinition.getArgToFocus(i + 1) - 1;\n if (0 <= argPosition && argPosition < functionDefinition.args.length) {\n const currentArg = currentFunctionArguments[i];\n const argDefinition = functionDefinition.args[argPosition];\n const argTypes = argDefinition.type || [];\n // detect when an argument need to be evaluated as a meta argument\n const isMeta = argTypes.includes(\"META\");\n // detect when an argument need to be evaluated as a lazy argument\n const isLazy = argDefinition.lazy;\n const hasRange = argTypes.some((t) => t === \"RANGE\" ||\n t === \"RANGE\" ||\n t === \"RANGE\" ||\n t === \"RANGE\" ||\n t === \"RANGE\");\n const isRangeOnly = argTypes.every((t) => t === \"RANGE\" ||\n t === \"RANGE\" ||\n t === \"RANGE\" ||\n t === \"RANGE\" ||\n t === \"RANGE\");\n if (isRangeOnly) {\n if (currentArg.type !== \"REFERENCE\") {\n throw new BadExpressionError(_lt(\"Function %s expects the parameter %s to be reference to a cell or range, not a %s.\", ast.value.toUpperCase(), (i + 1).toString(), currentArg.type.toLowerCase()));\n }\n }\n const compiledAST = compileAST(currentArg, isLazy, isMeta, hasRange, {\n functionName: ast.value.toUpperCase(),\n paramIndex: i + 1,\n });\n listArgs.push(compiledAST);\n }\n }\n return listArgs;\n }\n /**\n * This function compiles all the information extracted by the parser into an\n * executable code for the evaluation of the cells content. It uses a cash to\n * not reevaluate identical code structures.\n *\n * The function is sensitive to two parameters \u201cisLazy\u201d and \u201cisMeta\u201d. These\n * parameters may vary when compiling function arguments:\n *\n * - isLazy: In some cases the function arguments does not need to be\n * evaluated before entering the functions. For example the IF function might\n * take invalid arguments that do not need to be evaluate and thus should not\n * create an error. For this we have lazy arguments.\n *\n * - isMeta: In some cases the function arguments expects information on the\n * cell/range other than the associated value(s). For example the COLUMN\n * function needs to receive as argument the coordinates of a cell rather\n * than its value. For this we have meta arguments.\n */\n function compileAST(ast, isLazy = false, isMeta = false, hasRange = false, referenceVerification = {}) {\n const codeBlocks = [];\n let id, fnName, statement;\n if (ast.type !== \"REFERENCE\" && !(ast.type === \"BIN_OPERATION\" && ast.value === \":\")) {\n if (isMeta) {\n throw new BadExpressionError(_lt(`Argument must be a reference to a cell or range.`));\n }\n }\n if (ast.debug) {\n codeBlocks.push(\"debugger;\");\n }\n switch (ast.type) {\n case \"BOOLEAN\":\n if (!isLazy) {\n return { id: `{ value: ${ast.value} }`, code: \"\" };\n }\n id = nextId++;\n statement = `{ value: ${ast.value} }`;\n break;\n case \"NUMBER\":\n id = nextId++;\n statement = `{ value: this.constantValues.numbers[${constantValues.numbers.indexOf(ast.value)}] }`;\n break;\n case \"STRING\":\n id = nextId++;\n statement = `{ value: this.constantValues.strings[${constantValues.strings.indexOf(ast.value)}] }`;\n break;\n case \"REFERENCE\":\n const referenceIndex = dependencies.indexOf(ast.value);\n id = nextId++;\n if (hasRange) {\n statement = `range(deps[${referenceIndex}])`;\n }\n else {\n statement = `ref(deps[${referenceIndex}], ${isMeta ? \"true\" : \"false\"}, \"${referenceVerification.functionName || OPERATOR_MAP[\"=\"]}\", ${referenceVerification.paramIndex})`;\n }\n break;\n case \"FUNCALL\":\n id = nextId++;\n const args = compileFunctionArgs(ast);\n codeBlocks.push(args.map((arg) => arg.code).join(\"\\n\"));\n fnName = ast.value.toUpperCase();\n codeBlocks.push(`ctx.__lastFnCalled = '${fnName}';`);\n statement = `ctx['${fnName}'](${args.map((arg) => arg.id)})`;\n break;\n case \"UNARY_OPERATION\": {\n id = nextId++;\n fnName = UNARY_OPERATOR_MAP[ast.value];\n const operand = compileAST(ast.operand, false, false, false, {\n functionName: fnName,\n });\n codeBlocks.push(operand.code);\n codeBlocks.push(`ctx.__lastFnCalled = '${fnName}';`);\n statement = `ctx['${fnName}'](${operand.id})`;\n break;\n }\n case \"BIN_OPERATION\": {\n id = nextId++;\n fnName = OPERATOR_MAP[ast.value];\n const left = compileAST(ast.left, false, false, false, {\n functionName: fnName,\n });\n const right = compileAST(ast.right, false, false, false, {\n functionName: fnName,\n });\n codeBlocks.push(left.code);\n codeBlocks.push(right.code);\n codeBlocks.push(`ctx.__lastFnCalled = '${fnName}';`);\n statement = `ctx['${fnName}'](${left.id}, ${right.id})`;\n break;\n }\n case \"UNKNOWN\":\n if (!isLazy) {\n return { id: \"undefined\", code: \"\" };\n }\n id = nextId++;\n statement = `undefined`;\n break;\n }\n if (isLazy) {\n const lazyFunction = `const _${id} = () => {\\n` +\n `\\t${splitCodeLines(codeBlocks).join(\"\\n\\t\")}\\n` +\n `\\treturn ${statement};\\n` +\n \"}\";\n return { id: `_${id}`, code: lazyFunction };\n }\n else {\n codeBlocks.push(`let _${id} = ${statement};`);\n return { id: `_${id}`, code: codeBlocks.join(\"\\n\") };\n }\n }\n }\n const compiledFormula = {\n execute: functionCache[cacheKey].execute,\n dependencies,\n constantValues,\n tokens,\n };\n return compiledFormula;\n }\n /**\n * Compute a cache key for the formula.\n * References, numbers and strings are replaced with placeholders because\n * the compiled formula does not depend on their actual value.\n * Both `=A1+1+\"2\"` and `=A2+2+\"3\"` are compiled to the exact same function.\n *\n * Spaces are also ignored to compute the cache key.\n *\n * A formula `=A1+A2+SUM(2, 2, \"2\")` have the cache key `=|0|+|1|+SUM(|N0|,|N0|,|S0|)`\n */\n function compilationCacheKey(tokens, dependencies, constantValues) {\n return concat(tokens.map((token) => {\n switch (token.type) {\n case \"STRING\":\n const value = removeStringQuotes(token.value);\n return `|S${constantValues.strings.indexOf(value)}|`;\n case \"NUMBER\":\n return `|N${constantValues.numbers.indexOf(parseNumber(token.value))}|`;\n case \"REFERENCE\":\n case \"INVALID_REFERENCE\":\n return `|${dependencies.indexOf(token.value)}|`;\n case \"SPACE\":\n return \"\";\n default:\n return token.value;\n }\n }));\n }\n /**\n * Return formula arguments which are references, strings and numbers.\n */\n function formulaArguments(tokens) {\n const constantValues = {\n numbers: [],\n strings: [],\n };\n const dependencies = [];\n for (const token of tokens) {\n switch (token.type) {\n case \"INVALID_REFERENCE\":\n case \"REFERENCE\":\n dependencies.push(token.value);\n break;\n case \"STRING\":\n const value = removeStringQuotes(token.value);\n if (!constantValues.strings.includes(value)) {\n constantValues.strings.push(value);\n }\n break;\n case \"NUMBER\": {\n const value = parseNumber(token.value);\n if (!constantValues.numbers.includes(value)) {\n constantValues.numbers.push(value);\n }\n break;\n }\n }\n }\n return {\n dependencies,\n constantValues,\n };\n }\n\n /**\n * Add the following information on tokens:\n * - length\n * - start\n * - end\n */\n function enrichTokens(tokens) {\n let current = 0;\n return tokens.map((x) => {\n const len = x.value.toString().length;\n const token = Object.assign({}, x, {\n start: current,\n end: current + len,\n length: len,\n });\n current = token.end;\n return token;\n });\n }\n /**\n * add on each token the length, start and end\n * also matches the opening to its closing parenthesis (using the same number)\n */\n function mapParenthesis(tokens) {\n let maxParen = 1;\n const stack = [];\n return tokens.map((token) => {\n if (token.type === \"LEFT_PAREN\") {\n stack.push(maxParen);\n token.parenIndex = maxParen;\n maxParen++;\n }\n else if (token.type === \"RIGHT_PAREN\") {\n token.parenIndex = stack.pop();\n }\n return token;\n });\n }\n /**\n * add on each token its parent function and the index corresponding to\n * its position as an argument of the function.\n * In this example \"=MIN(42,SUM(MAX(1,2),3))\":\n * - the parent function of the token correspond to number 42 is the MIN function\n * - the argument position of the token correspond to number 42 is 0\n * - the parent function of the token correspond to number 3 is the SUM function\n * - the argument position of the token correspond to number 3 is 1\n */\n function mapParentFunction(tokens) {\n let stack = [];\n let functionStarted = \"\";\n const res = tokens.map((token, i) => {\n if (![\"SPACE\", \"LEFT_PAREN\"].includes(token.type)) {\n functionStarted = \"\";\n }\n switch (token.type) {\n case \"FUNCTION\":\n functionStarted = token.value;\n break;\n case \"LEFT_PAREN\":\n stack.push({ parent: functionStarted, argPosition: 0 });\n functionStarted = \"\";\n break;\n case \"RIGHT_PAREN\":\n stack.pop();\n break;\n case \"COMMA\":\n if (stack.length) {\n // increment position on current function\n stack[stack.length - 1].argPosition++;\n }\n break;\n }\n if (stack.length) {\n const functionContext = stack[stack.length - 1];\n if (functionContext.parent) {\n token.functionContext = Object.assign({}, functionContext);\n }\n }\n return token;\n });\n return res;\n }\n /**\n * Take the result of the tokenizer and transform it to be usable in the composer.\n *\n * @param formula\n */\n function composerTokenize(formula) {\n const tokens = rangeTokenize(formula);\n return mapParentFunction(mapParenthesis(enrichTokens(tokens)));\n }\n\n /**\n * Change the reference types inside the given token, if the token represent a range or a cell\n *\n * Eg. :\n * A1 => $A$1 => A$1 => $A1 => A1\n * A1:$B$1 => $A$1:B$1 => A$1:$B1 => $A1:B1 => A1:$B$1\n */\n function loopThroughReferenceType(token) {\n if (token.type !== \"REFERENCE\")\n return token;\n const { xc, sheetName } = splitReference(token.value);\n const [left, right] = xc.split(\":\");\n const sheetRef = sheetName ? `${getComposerSheetName(sheetName)}!` : \"\";\n const updatedLeft = getTokenNextReferenceType(left);\n const updatedRight = right ? `:${getTokenNextReferenceType(right)}` : \"\";\n return { ...token, value: sheetRef + updatedLeft + updatedRight };\n }\n /**\n * Get a new token with a changed type of reference from the given cell token symbol.\n * Undefined behavior if given a token other than a cell or if the Xc contains a sheet reference\n *\n * A1 => $A$1 => A$1 => $A1 => A1\n */\n function getTokenNextReferenceType(xc) {\n switch (getReferenceType(xc)) {\n case \"none\":\n xc = setXcToReferenceType(xc, \"colrow\");\n break;\n case \"colrow\":\n xc = setXcToReferenceType(xc, \"row\");\n break;\n case \"row\":\n xc = setXcToReferenceType(xc, \"col\");\n break;\n case \"col\":\n xc = setXcToReferenceType(xc, \"none\");\n break;\n }\n return xc;\n }\n /**\n * Returns the given XC with the given reference type.\n */\n function setXcToReferenceType(xc, referenceType) {\n xc = xc.replace(/\\$/g, \"\");\n let indexOfNumber;\n switch (referenceType) {\n case \"col\":\n return \"$\" + xc;\n case \"row\":\n indexOfNumber = xc.search(/[0-9]/);\n return xc.slice(0, indexOfNumber) + \"$\" + xc.slice(indexOfNumber);\n case \"colrow\":\n indexOfNumber = xc.search(/[0-9]/);\n xc = xc.slice(0, indexOfNumber) + \"$\" + xc.slice(indexOfNumber);\n return \"$\" + xc;\n case \"none\":\n return xc;\n }\n }\n /**\n * Return the type of reference used in the given XC of a cell.\n * Undefined behavior if the XC have a sheet reference\n */\n function getReferenceType(xcCell) {\n if (isColAndRowFixed(xcCell)) {\n return \"colrow\";\n }\n else if (isColFixed(xcCell)) {\n return \"col\";\n }\n else if (isRowFixed(xcCell)) {\n return \"row\";\n }\n return \"none\";\n }\n function isColFixed(xc) {\n return xc.startsWith(\"$\");\n }\n function isRowFixed(xc) {\n return xc.includes(\"$\", 1);\n }\n function isColAndRowFixed(xc) {\n return xc.startsWith(\"$\") && xc.length > 1 && xc.slice(1).includes(\"$\");\n }\n\n /**\n * BasePlugin\n *\n * Since the spreadsheet internal state is quite complex, it is split into\n * multiple parts, each managing a specific concern.\n *\n * This file introduce the BasePlugin, which is the common class that defines\n * how each of these model sub parts should interact with each other.\n * There are two kind of plugins: core plugins handling persistent data\n * and UI plugins handling transient data.\n */\n class BasePlugin {\n constructor(stateObserver, dispatch) {\n this.history = Object.assign(Object.create(stateObserver), {\n update: stateObserver.addChange.bind(stateObserver, this),\n selectCell: () => { },\n });\n this.dispatch = dispatch;\n }\n /**\n * Export for excel should be available for all plugins, even for the UI.\n * In some case, we need to export evaluated value, which is available from\n * UI plugin only.\n */\n exportForExcel(data) { }\n // ---------------------------------------------------------------------------\n // Command handling\n // ---------------------------------------------------------------------------\n /**\n * Before a command is accepted, the model will ask each plugin if the command\n * is allowed. If all of then return true, then we can proceed. Otherwise,\n * the command is cancelled.\n *\n * There should not be any side effects in this method.\n */\n allowDispatch(command) {\n return 0 /* CommandResult.Success */;\n }\n /**\n * This method is useful when a plugin need to perform some action before a\n * command is handled in another plugin. This should only be used if it is not\n * possible to do the work in the handle method.\n */\n beforeHandle(command) { }\n /**\n * This is the standard place to handle any command. Most of the plugin\n * command handling work should take place here.\n */\n handle(command) { }\n /**\n * Sometimes, it is useful to perform some work after a command (and all its\n * subcommands) has been completely handled. For example, when we paste\n * multiple cells, we only want to reevaluate the cell values once at the end.\n */\n finalize() { }\n /**\n * Combine multiple validation functions into a single function\n * returning the list of result of every validation.\n */\n batchValidations(...validations) {\n return (toValidate) => validations.map((validation) => validation.call(this, toValidate)).flat();\n }\n /**\n * Combine multiple validation functions. Every validation is executed one after\n * the other. As soon as one validation fails, it stops and the cancelled reason\n * is returned.\n */\n chainValidations(...validations) {\n return (toValidate) => {\n for (const validation of validations) {\n let results = validation.call(this, toValidate);\n if (!Array.isArray(results)) {\n results = [results];\n }\n const cancelledReasons = results.filter((result) => result !== 0 /* CommandResult.Success */);\n if (cancelledReasons.length) {\n return cancelledReasons;\n }\n }\n return 0 /* CommandResult.Success */;\n };\n }\n checkValidations(command, ...validations) {\n return this.batchValidations(...validations)(command);\n }\n }\n BasePlugin.getters = [];\n\n /**\n * UI plugins handle any transient data required to display a spreadsheet.\n * They can draw on the grid canvas.\n */\n class UIPlugin extends BasePlugin {\n constructor({ getters, stateObserver, dispatch, uiActions, selection }) {\n super(stateObserver, dispatch);\n this.getters = getters;\n this.ui = uiActions;\n this.selection = selection;\n }\n // ---------------------------------------------------------------------------\n // Grid rendering\n // ---------------------------------------------------------------------------\n drawGrid(ctx, layer) { }\n }\n UIPlugin.layers = [];\n\n const CELL_DELETED_MESSAGE = _lt(\"The cell you are trying to edit has been deleted.\");\n const SelectionIndicator = \"\u2423\";\n class EditionPlugin extends UIPlugin {\n constructor() {\n super(...arguments);\n this.col = 0;\n this.row = 0;\n this.mode = \"inactive\";\n this.sheetId = \"\";\n this.currentContent = \"\";\n this.currentTokens = [];\n this.selectionStart = 0;\n this.selectionEnd = 0;\n this.selectionInitialStart = 0;\n this.initialContent = \"\";\n this.previousRef = \"\";\n this.previousRange = undefined;\n this.colorIndexByRange = {};\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"CHANGE_COMPOSER_CURSOR_SELECTION\":\n return this.validateSelection(this.currentContent.length, cmd.start, cmd.end);\n case \"SET_CURRENT_CONTENT\":\n if (cmd.selection) {\n return this.validateSelection(cmd.content.length, cmd.selection.start, cmd.selection.end);\n }\n else {\n return 0 /* CommandResult.Success */;\n }\n case \"START_EDITION\":\n if (cmd.selection) {\n const content = cmd.text || this.getComposerContent(this.getters.getActivePosition());\n return this.validateSelection(content.length, cmd.selection.start, cmd.selection.end);\n }\n else {\n return 0 /* CommandResult.Success */;\n }\n default:\n return 0 /* CommandResult.Success */;\n }\n }\n handleEvent(event) {\n if (this.mode !== \"selecting\") {\n return;\n }\n switch (event.mode) {\n case \"newAnchor\":\n this.insertSelectedRange(event.anchor.zone);\n break;\n default:\n this.replaceSelectedRanges(event.anchor.zone);\n break;\n }\n }\n handle(cmd) {\n switch (cmd.type) {\n case \"CHANGE_COMPOSER_CURSOR_SELECTION\":\n this.selectionStart = cmd.start;\n this.selectionEnd = cmd.end;\n break;\n case \"STOP_COMPOSER_RANGE_SELECTION\":\n if (this.isSelectingForComposer()) {\n this.mode = \"editing\";\n }\n break;\n case \"START_EDITION\":\n this.startEdition(cmd.text, cmd.selection);\n this.updateRangeColor();\n break;\n case \"STOP_EDITION\":\n if (cmd.cancel) {\n this.cancelEditionAndActivateSheet();\n this.resetContent();\n }\n else {\n this.stopEdition();\n }\n this.colorIndexByRange = {};\n break;\n case \"SET_CURRENT_CONTENT\":\n this.setContent(cmd.content, cmd.selection, true);\n this.updateRangeColor();\n break;\n case \"REPLACE_COMPOSER_CURSOR_SELECTION\":\n this.replaceSelection(cmd.text);\n break;\n case \"SELECT_FIGURE\":\n this.cancelEditionAndActivateSheet();\n this.resetContent();\n break;\n case \"ADD_COLUMNS_ROWS\":\n this.onAddElements(cmd);\n break;\n case \"REMOVE_COLUMNS_ROWS\":\n if (cmd.dimension === \"COL\") {\n this.onColumnsRemoved(cmd);\n }\n else {\n this.onRowsRemoved(cmd);\n }\n break;\n case \"START_CHANGE_HIGHLIGHT\":\n this.dispatch(\"STOP_COMPOSER_RANGE_SELECTION\");\n const range = this.getters.getRangeFromRangeData(cmd.range);\n const previousRefToken = this.currentTokens\n .filter((token) => token.type === \"REFERENCE\")\n .find((token) => {\n const { xc, sheetName: sheet } = splitReference(token.value);\n const sheetName = sheet || this.getters.getSheetName(this.sheetId);\n const activeSheetId = this.getters.getActiveSheetId();\n if (this.getters.getSheetName(activeSheetId) !== sheetName) {\n return false;\n }\n const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);\n return isEqual(this.getters.expandZone(activeSheetId, refRange.zone), range.zone);\n });\n this.previousRef = previousRefToken.value;\n this.previousRange = this.getters.getRangeFromSheetXC(this.getters.getActiveSheetId(), this.previousRef);\n this.selectionInitialStart = previousRefToken.start;\n break;\n case \"CHANGE_HIGHLIGHT\":\n const cmdRange = this.getters.getRangeFromRangeData(cmd.range);\n const newRef = this.getRangeReference(cmdRange, this.previousRange.parts);\n this.selectionStart = this.selectionInitialStart;\n this.selectionEnd = this.selectionInitialStart + this.previousRef.length;\n this.replaceSelection(newRef);\n this.previousRef = newRef;\n this.selectionStart = this.currentContent.length;\n this.selectionEnd = this.currentContent.length;\n break;\n case \"ACTIVATE_SHEET\":\n if (!this.currentContent.startsWith(\"=\")) {\n this.cancelEdition();\n this.resetContent();\n }\n if (cmd.sheetIdFrom !== cmd.sheetIdTo) {\n const activePosition = this.getters.getActivePosition();\n const { col, row } = this.getters.getNextVisibleCellPosition({\n sheetId: cmd.sheetIdTo,\n col: activePosition.col,\n row: activePosition.row,\n });\n const zone = this.getters.expandZone(cmd.sheetIdTo, positionToZone({ col, row }));\n this.selection.resetAnchor(this, { cell: { col, row }, zone });\n }\n break;\n case \"DELETE_SHEET\":\n case \"UNDO\":\n case \"REDO\":\n const sheetIdExists = !!this.getters.tryGetSheet(this.sheetId);\n if (!sheetIdExists && this.mode !== \"inactive\") {\n this.sheetId = this.getters.getActiveSheetId();\n this.cancelEditionAndActivateSheet();\n this.resetContent();\n this.ui.notifyUI({\n type: \"ERROR\",\n text: CELL_DELETED_MESSAGE,\n });\n }\n break;\n case \"CYCLE_EDITION_REFERENCES\":\n this.cycleReferences();\n break;\n }\n }\n unsubscribe() {\n this.mode = \"inactive\";\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n getEditionMode() {\n return this.mode;\n }\n getCurrentContent() {\n if (this.mode === \"inactive\") {\n return this.getComposerContent(this.getters.getActivePosition());\n }\n return this.currentContent;\n }\n getEditionSheet() {\n return this.sheetId;\n }\n getComposerSelection() {\n return {\n start: this.selectionStart,\n end: this.selectionEnd,\n };\n }\n isSelectingForComposer() {\n return this.mode === \"selecting\";\n }\n showSelectionIndicator() {\n return this.isSelectingForComposer() && this.canStartComposerRangeSelection();\n }\n getCurrentTokens() {\n return this.currentTokens;\n }\n /**\n * Return the (enriched) token just before the cursor.\n */\n getTokenAtCursor() {\n const start = Math.min(this.selectionStart, this.selectionEnd);\n const end = Math.max(this.selectionStart, this.selectionEnd);\n if (start === end && end === 0) {\n return undefined;\n }\n else {\n return this.currentTokens.find((t) => t.start <= start && t.end >= end);\n }\n }\n // ---------------------------------------------------------------------------\n // Misc\n // ---------------------------------------------------------------------------\n cycleReferences() {\n const tokens = this.getTokensInSelection();\n const refTokens = tokens.filter((token) => token.type === \"REFERENCE\");\n if (refTokens.length === 0)\n return;\n const updatedReferences = tokens\n .map(loopThroughReferenceType)\n .map((token) => token.value)\n .join(\"\");\n const content = this.currentContent;\n const start = tokens[0].start;\n const end = tokens[tokens.length - 1].end;\n const newContent = content.slice(0, start) + updatedReferences + content.slice(end);\n const lengthDiff = newContent.length - content.length;\n const startOfTokens = refTokens[0].start;\n const endOfTokens = refTokens[refTokens.length - 1].end + lengthDiff;\n const selection = { start: startOfTokens, end: endOfTokens };\n // Put the selection at the end of the token if we cycled on a single token\n if (refTokens.length === 1 && this.selectionStart === this.selectionEnd) {\n selection.start = selection.end;\n }\n this.dispatch(\"SET_CURRENT_CONTENT\", {\n content: newContent,\n selection,\n });\n }\n validateSelection(length, start, end) {\n return start >= 0 && start <= length && end >= 0 && end <= length\n ? 0 /* CommandResult.Success */\n : 46 /* CommandResult.WrongComposerSelection */;\n }\n onColumnsRemoved(cmd) {\n if (cmd.elements.includes(this.col) && this.mode !== \"inactive\") {\n this.dispatch(\"STOP_EDITION\", { cancel: true });\n this.ui.notifyUI({\n type: \"ERROR\",\n text: CELL_DELETED_MESSAGE,\n });\n return;\n }\n const { top, left } = updateSelectionOnDeletion({ left: this.col, right: this.col, top: this.row, bottom: this.row }, \"left\", [...cmd.elements]);\n this.col = left;\n this.row = top;\n }\n onRowsRemoved(cmd) {\n if (cmd.elements.includes(this.row) && this.mode !== \"inactive\") {\n this.dispatch(\"STOP_EDITION\", { cancel: true });\n this.ui.notifyUI({\n type: \"ERROR\",\n text: CELL_DELETED_MESSAGE,\n });\n return;\n }\n const { top, left } = updateSelectionOnDeletion({ left: this.col, right: this.col, top: this.row, bottom: this.row }, \"top\", [...cmd.elements]);\n this.col = left;\n this.row = top;\n }\n onAddElements(cmd) {\n const { top, left } = updateSelectionOnInsertion({ left: this.col, right: this.col, top: this.row, bottom: this.row }, cmd.dimension === \"COL\" ? \"left\" : \"top\", cmd.base, cmd.position, cmd.quantity);\n this.col = left;\n this.row = top;\n }\n /**\n * Enable the selecting mode\n */\n startComposerRangeSelection() {\n if (this.sheetId === this.getters.getActiveSheetId()) {\n const zone = positionToZone({ col: this.col, row: this.row });\n this.selection.resetAnchor(this, { cell: { col: this.col, row: this.row }, zone });\n }\n this.mode = \"selecting\";\n this.selectionInitialStart = this.selectionStart;\n }\n /**\n * start the edition of a cell\n * @param str the key that is used to start the edition if it is a \"content\" key like a letter or number\n * @param selection\n * @private\n */\n startEdition(str, selection) {\n var _a;\n const evaluatedCell = this.getters.getActiveCell();\n if (str && ((_a = evaluatedCell.format) === null || _a === void 0 ? void 0 : _a.includes(\"%\")) && isNumber(str)) {\n selection = selection || { start: str.length, end: str.length };\n str = `${str}%`;\n }\n const sheetId = this.getters.getActiveSheetId();\n const { col, row } = this.getters.getActivePosition();\n this.col = col;\n this.sheetId = sheetId;\n this.row = row;\n this.initialContent = this.getComposerContent({ sheetId, col, row });\n this.mode = \"editing\";\n this.setContent(str || this.initialContent, selection);\n this.colorIndexByRange = {};\n const zone = positionToZone({ col: this.col, row: this.row });\n this.selection.capture(this, { cell: { col: this.col, row: this.row }, zone }, {\n handleEvent: this.handleEvent.bind(this),\n release: () => {\n this.stopEdition();\n },\n });\n }\n stopEdition() {\n if (this.mode !== \"inactive\") {\n this.cancelEditionAndActivateSheet();\n const col = this.col;\n const row = this.row;\n let content = this.currentContent;\n const didChange = this.initialContent !== content;\n if (!didChange) {\n return;\n }\n if (content) {\n const sheetId = this.getters.getActiveSheetId();\n const cell = this.getters.getEvaluatedCell({ sheetId, col: this.col, row: this.row });\n if (content.startsWith(\"=\")) {\n const left = this.currentTokens.filter((t) => t.type === \"LEFT_PAREN\").length;\n const right = this.currentTokens.filter((t) => t.type === \"RIGHT_PAREN\").length;\n const missing = left - right;\n if (missing > 0) {\n content += concat(new Array(missing).fill(\")\"));\n }\n }\n else if (cell.link) {\n content = markdownLink(content, cell.link.url);\n }\n this.dispatch(\"UPDATE_CELL\", {\n sheetId: this.sheetId,\n col,\n row,\n content,\n });\n }\n else {\n this.dispatch(\"UPDATE_CELL\", {\n sheetId: this.sheetId,\n content: \"\",\n col,\n row,\n });\n }\n this.setContent(\"\");\n }\n }\n cancelEditionAndActivateSheet() {\n if (this.mode === \"inactive\") {\n return;\n }\n this.cancelEdition();\n const sheetId = this.getters.getActiveSheetId();\n if (sheetId !== this.sheetId) {\n this.dispatch(\"ACTIVATE_SHEET\", {\n sheetIdFrom: this.getters.getActiveSheetId(),\n sheetIdTo: this.sheetId,\n });\n }\n }\n getComposerContent(position) {\n const cell = this.getters.getCell(position);\n if (cell === null || cell === void 0 ? void 0 : cell.isFormula) {\n return cell.content;\n }\n const { format, value, type, formattedValue } = this.getters.getEvaluatedCell(position);\n switch (type) {\n case CellValueType.text:\n case CellValueType.empty:\n return value;\n case CellValueType.boolean:\n return formattedValue;\n case CellValueType.error:\n return (cell === null || cell === void 0 ? void 0 : cell.content) || \"\";\n case CellValueType.number:\n if (format && isDateTimeFormat(format)) {\n return formattedValue;\n }\n return this.numberComposerContent(value, format);\n }\n }\n numberComposerContent(value, format) {\n if (format === null || format === void 0 ? void 0 : format.includes(\"%\")) {\n return `${value * 100}%`;\n }\n return numberToString(value);\n }\n cancelEdition() {\n if (this.mode === \"inactive\") {\n return;\n }\n this.mode = \"inactive\";\n this.selection.release(this);\n }\n /**\n * Reset the current content to the active cell content\n */\n resetContent() {\n this.setContent(this.initialContent || \"\");\n }\n setContent(text, selection, raise) {\n text = text.replace(/[\\r\\n]/g, \"\");\n const isNewCurrentContent = this.currentContent !== text;\n this.currentContent = text;\n if (selection) {\n this.selectionStart = selection.start;\n this.selectionEnd = selection.end;\n }\n else {\n this.selectionStart = this.selectionEnd = text.length;\n }\n if (isNewCurrentContent || this.mode !== \"inactive\") {\n this.currentTokens = text.startsWith(\"=\") ? composerTokenize(text) : [];\n if (this.currentTokens.length > 100) {\n if (raise) {\n this.ui.notifyUI({\n type: \"ERROR\",\n text: _lt(\"This formula has over 100 parts. It can't be processed properly, consider splitting it into multiple cells\"),\n });\n }\n }\n }\n if (this.canStartComposerRangeSelection()) {\n this.startComposerRangeSelection();\n }\n }\n insertSelectedRange(zone) {\n // infer if range selected or selecting range from cursor position\n const start = Math.min(this.selectionStart, this.selectionEnd);\n const ref = this.getZoneReference(zone);\n if (this.canStartComposerRangeSelection()) {\n this.insertText(ref, start);\n this.selectionInitialStart = start;\n }\n else {\n this.insertText(\",\" + ref, start);\n this.selectionInitialStart = start + 1;\n }\n }\n /**\n * Replace the current reference selected by the new one.\n * */\n replaceSelectedRanges(zone) {\n const ref = this.getZoneReference(zone);\n this.replaceText(ref, this.selectionInitialStart, this.selectionEnd);\n }\n getZoneReference(zone, fixedParts = [{ colFixed: false, rowFixed: false }]) {\n const sheetId = this.getters.getActiveSheetId();\n let selectedXc = this.getters.zoneToXC(sheetId, zone, fixedParts);\n if (this.getters.getEditionSheet() !== this.getters.getActiveSheetId()) {\n const sheetName = getComposerSheetName(this.getters.getSheetName(this.getters.getActiveSheetId()));\n selectedXc = `${sheetName}!${selectedXc}`;\n }\n return selectedXc;\n }\n getRangeReference(range, fixedParts = [{ colFixed: false, rowFixed: false }]) {\n let _fixedParts = [...fixedParts];\n if (fixedParts.length === 1 && getZoneArea(range.zone) > 1) {\n _fixedParts.push({ ...fixedParts[0] });\n }\n else if (fixedParts.length === 2 && getZoneArea(range.zone) === 1) {\n _fixedParts.pop();\n }\n const newRange = range.clone({ parts: _fixedParts });\n return this.getters.getSelectionRangeString(newRange, this.getters.getEditionSheet());\n }\n /**\n * Replace the current selection by a new text.\n * The cursor is then set at the end of the text.\n */\n replaceSelection(text) {\n const start = Math.min(this.selectionStart, this.selectionEnd);\n const end = Math.max(this.selectionStart, this.selectionEnd);\n this.replaceText(text, start, end);\n }\n replaceText(text, start, end) {\n this.currentContent =\n this.currentContent.slice(0, start) +\n this.currentContent.slice(end, this.currentContent.length);\n this.insertText(text, start);\n }\n /**\n * Insert a text at the given position.\n * The cursor is then set at the end of the text.\n */\n insertText(text, start) {\n const content = this.currentContent.slice(0, start) + text + this.currentContent.slice(start);\n const end = start + text.length;\n this.dispatch(\"SET_CURRENT_CONTENT\", {\n content,\n selection: { start: end, end },\n });\n }\n updateRangeColor() {\n if (!this.currentContent.startsWith(\"=\") || this.mode === \"inactive\") {\n return;\n }\n const editionSheetId = this.getters.getEditionSheet();\n const XCs = this.getReferencedRanges().map((range) => this.getters.getRangeString(range, editionSheetId));\n const colorsToKeep = {};\n for (const xc of XCs) {\n if (this.colorIndexByRange[xc] !== undefined) {\n colorsToKeep[xc] = this.colorIndexByRange[xc];\n }\n }\n const usedIndexes = new Set(Object.values(colorsToKeep));\n let currentIndex = 0;\n const nextIndex = () => {\n while (usedIndexes.has(currentIndex))\n currentIndex++;\n usedIndexes.add(currentIndex);\n return currentIndex;\n };\n for (const xc of XCs) {\n const colorIndex = xc in colorsToKeep ? colorsToKeep[xc] : nextIndex();\n colorsToKeep[xc] = colorIndex;\n }\n this.colorIndexByRange = colorsToKeep;\n }\n /**\n * Highlight all ranges that can be found in the composer content.\n */\n getComposerHighlights() {\n if (!this.currentContent.startsWith(\"=\") || this.mode === \"inactive\") {\n return [];\n }\n const editionSheetId = this.getters.getEditionSheet();\n const rangeColor = (rangeString) => {\n const colorIndex = this.colorIndexByRange[rangeString];\n return colors$1[colorIndex % colors$1.length];\n };\n return this.getReferencedRanges().map((range) => {\n const rangeString = this.getters.getRangeString(range, editionSheetId);\n return {\n zone: range.zone,\n color: rangeColor(rangeString),\n sheetId: range.sheetId,\n };\n });\n }\n /**\n * Return ranges currently referenced in the composer\n */\n getReferencedRanges() {\n const editionSheetId = this.getters.getEditionSheet();\n return this.currentTokens\n .filter((token) => token.type === \"REFERENCE\")\n .map((token) => this.getters.getRangeFromSheetXC(editionSheetId, token.value));\n }\n /**\n * Function used to determine when composer selection can start.\n * Three conditions are necessary:\n * - the previous token is among [\"COMMA\", \"LEFT_PAREN\", \"OPERATOR\"], and is not a postfix unary operator\n * - the next token is missing or is among [\"COMMA\", \"RIGHT_PAREN\", \"OPERATOR\"]\n * - Previous and next tokens can be separated by spaces\n */\n canStartComposerRangeSelection() {\n if (this.currentContent.startsWith(\"=\")) {\n const tokenAtCursor = this.getTokenAtCursor();\n if (!tokenAtCursor) {\n return false;\n }\n const tokenIdex = this.currentTokens.map((token) => token.start).indexOf(tokenAtCursor.start);\n let count = tokenIdex;\n let currentToken = tokenAtCursor;\n // check previous token\n while (![\"COMMA\", \"LEFT_PAREN\", \"OPERATOR\"].includes(currentToken.type) ||\n POSTFIX_UNARY_OPERATORS.includes(currentToken.value)) {\n if (currentToken.type !== \"SPACE\" || count < 1) {\n return false;\n }\n count--;\n currentToken = this.currentTokens[count];\n }\n count = tokenIdex + 1;\n currentToken = this.currentTokens[count];\n // check next token\n while (currentToken && ![\"COMMA\", \"RIGHT_PAREN\", \"OPERATOR\"].includes(currentToken.type)) {\n if (currentToken.type !== \"SPACE\") {\n return false;\n }\n count++;\n currentToken = this.currentTokens[count];\n }\n return true;\n }\n return false;\n }\n /**\n * Return all the tokens between selectionStart and selectionEnd.\n * Includes token that begin right on selectionStart or end right on selectionEnd.\n */\n getTokensInSelection() {\n const start = Math.min(this.selectionStart, this.selectionEnd);\n const end = Math.max(this.selectionStart, this.selectionEnd);\n return this.currentTokens.filter((t) => (t.start <= start && t.end >= start) || (t.start >= start && t.start < end));\n }\n }\n EditionPlugin.getters = [\n \"getEditionMode\",\n \"isSelectingForComposer\",\n \"showSelectionIndicator\",\n \"getCurrentContent\",\n \"getEditionSheet\",\n \"getComposerSelection\",\n \"getCurrentTokens\",\n \"getTokenAtCursor\",\n \"getComposerHighlights\",\n ];\n\n const arrowMap = {\n ArrowDown: \"down\",\n ArrowLeft: \"left\",\n ArrowRight: \"right\",\n ArrowUp: \"up\",\n };\n function updateSelectionWithArrowKeys(ev, selection) {\n const direction = arrowMap[ev.key];\n if (ev.shiftKey) {\n selection.resizeAnchorZone(direction, ev.ctrlKey ? \"end\" : 1);\n }\n else {\n selection.moveAnchorCell(direction, ev.ctrlKey ? \"end\" : 1);\n }\n }\n\n css /* scss */ `\n .o-autocomplete-dropdown {\n pointer-events: auto;\n background-color: #fff;\n & > div:hover {\n background-color: #f2f2f2;\n }\n .o-autocomplete-value-focus {\n background-color: rgba(0, 0, 0, 0.08);\n }\n\n & > div {\n display: flex;\n flex-direction: column;\n padding: 1px 0 5px 5px;\n .o-autocomplete-description {\n padding: 0 0 0 5px;\n font-size: 11px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n }\n }\n`;\n class TextValueProvider extends owl.Component {\n }\n TextValueProvider.template = \"o-spreadsheet-TextValueProvider\";\n TextValueProvider.props = {\n values: Array,\n selectedIndex: Number,\n onValueSelected: Function,\n };\n\n class ContentEditableHelper {\n constructor(el) {\n this.el = el;\n }\n updateEl(el) {\n this.el = el;\n }\n /**\n * select the text at position start to end, no matter the children\n */\n selectRange(start, end) {\n let selection = window.getSelection();\n this.removeSelection();\n let range = document.createRange();\n if (start == end && start === 0) {\n range.setStart(this.el, 0);\n range.setEnd(this.el, 0);\n selection.addRange(range);\n }\n else {\n if (start < 0 || end > this.el.textContent.length) {\n console.warn(`wrong selection asked start ${start}, end ${end}, text content length ${this.el.textContent.length}`);\n if (start < 0)\n start = 0;\n if (end > this.el.textContent.length)\n end = this.el.textContent.length;\n if (start > this.el.textContent.length)\n start = this.el.textContent.length;\n }\n let startNode = this.findChildAtCharacterIndex(start);\n let endNode = this.findChildAtCharacterIndex(end);\n range.setStart(startNode.node, startNode.offset);\n selection.addRange(range);\n selection.extend(endNode.node, endNode.offset);\n }\n }\n /**\n * finds the dom element that contains the character at `offset`\n */\n findChildAtCharacterIndex(offset) {\n let it = this.iterateChildren(this.el);\n let current, previous;\n let usedCharacters = offset;\n do {\n current = it.next();\n if (!current.done && !current.value.hasChildNodes()) {\n if (current.value.textContent && current.value.textContent.length < usedCharacters) {\n usedCharacters -= current.value.textContent.length;\n }\n else {\n it.return(current.value);\n }\n previous = current.value;\n }\n } while (!current.done);\n if (current.value) {\n return { node: current.value, offset: usedCharacters };\n }\n return { node: previous, offset: usedCharacters };\n }\n /**\n * Iterate over the dom tree starting at `el` and over all the children depth first.\n * */\n *iterateChildren(el) {\n yield el;\n if (el.hasChildNodes()) {\n for (let child of el.childNodes) {\n yield* this.iterateChildren(child);\n }\n }\n }\n /**\n * Sets (or Replaces all) the text inside the root element in the form of distinctive\n * span for each element provided in `contents`.\n *\n * Each span will have its own fontcolor and specific class if provided in the HtmlContent object.\n */\n setText(contents) {\n if (contents.length === 0) {\n return;\n }\n for (const content of contents) {\n const span = document.createElement(\"span\");\n span.innerText = content.value;\n if (content.color) {\n span.style.color = content.color;\n }\n if (content.class) {\n span.classList.add(content.class);\n }\n this.el.appendChild(span);\n }\n }\n /**\n * remove the current selection of the user\n * */\n removeSelection() {\n let selection = window.getSelection();\n selection.removeAllRanges();\n }\n removeAll() {\n if (this.el) {\n while (this.el.firstChild) {\n this.el.removeChild(this.el.firstChild);\n }\n }\n }\n /**\n * finds the indexes of the current selection.\n * */\n getCurrentSelection() {\n let { startElement, endElement, startSelectionOffset, endSelectionOffset } = this.getStartAndEndSelection();\n let startSizeBefore = this.findSizeBeforeElement(startElement);\n let endSizeBefore = this.findSizeBeforeElement(endElement);\n return {\n start: startSizeBefore + startSelectionOffset,\n end: endSizeBefore + endSelectionOffset,\n };\n }\n findSizeBeforeElement(nodeToFind) {\n let it = this.iterateChildren(this.el);\n let usedCharacters = 0;\n let current = it.next();\n while (!current.done && current.value !== nodeToFind) {\n if (!current.value.hasChildNodes()) {\n if (current.value.textContent) {\n usedCharacters += current.value.textContent.length;\n }\n }\n current = it.next();\n }\n return usedCharacters;\n }\n getStartAndEndSelection() {\n const selection = document.getSelection();\n return {\n startElement: selection.anchorNode || this.el,\n startSelectionOffset: selection.anchorOffset,\n endElement: selection.focusNode || this.el,\n endSelectionOffset: selection.focusOffset,\n };\n }\n }\n\n // -----------------------------------------------------------------------------\n // Formula Assistant component\n // -----------------------------------------------------------------------------\n css /* scss */ `\n .o-formula-assistant {\n white-space: normal;\n background-color: #fff;\n .o-formula-assistant-head {\n background-color: #f2f2f2;\n padding: 10px;\n }\n .o-formula-assistant-core {\n padding: 0px 0px 10px 0px;\n margin: 10px;\n border-bottom: 1px solid gray;\n }\n .o-formula-assistant-arg {\n padding: 0px 10px 10px 10px;\n display: flex;\n flex-direction: column;\n }\n .o-formula-assistant-arg-description {\n font-size: 85%;\n }\n .o-formula-assistant-focus {\n div:first-child,\n span {\n color: purple;\n text-shadow: 0px 0px 1px purple;\n }\n div:last-child {\n color: black;\n }\n }\n .o-formula-assistant-gray {\n color: gray;\n }\n }\n .o-formula-assistant-container {\n user-select: none;\n }\n .o-formula-assistant-event-none {\n pointer-events: none;\n }\n .o-formula-assistant-event-auto {\n pointer-events: auto;\n }\n .o-formula-assistant-transparency {\n opacity: 0.3;\n }\n`;\n class FunctionDescriptionProvider extends owl.Component {\n constructor() {\n super(...arguments);\n this.assistantState = owl.useState({\n allowCellSelectionBehind: false,\n });\n this.timeOutId = 0;\n }\n setup() {\n owl.onWillUnmount(() => {\n if (this.timeOutId) {\n clearTimeout(this.timeOutId);\n }\n });\n }\n getContext() {\n return this.props;\n }\n onMouseMove() {\n this.assistantState.allowCellSelectionBehind = true;\n if (this.timeOutId) {\n clearTimeout(this.timeOutId);\n }\n this.timeOutId = setTimeout(() => {\n this.assistantState.allowCellSelectionBehind = false;\n }, 2000);\n }\n }\n FunctionDescriptionProvider.template = \"o-spreadsheet-FunctionDescriptionProvider\";\n FunctionDescriptionProvider.props = {\n functionName: String,\n functionDescription: Object,\n argToFocus: Number,\n };\n\n const functions = functionRegistry.content;\n const ASSISTANT_WIDTH = 300;\n const FunctionColor = \"#4a4e4d\";\n const OperatorColor = \"#3da4ab\";\n const StringColor = \"#00a82d\";\n const SelectionIndicatorColor = \"darkgrey\";\n const NumberColor = \"#02c39a\";\n const MatchingParenColor = \"black\";\n const SelectionIndicatorClass = \"selector-flag\";\n const tokenColor = {\n OPERATOR: OperatorColor,\n NUMBER: NumberColor,\n STRING: StringColor,\n FUNCTION: FunctionColor,\n DEBUGGER: OperatorColor,\n LEFT_PAREN: FunctionColor,\n RIGHT_PAREN: FunctionColor,\n COMMA: FunctionColor,\n };\n css /* scss */ `\n .o-composer-container {\n padding: 0;\n margin: 0;\n border: 0;\n z-index: ${ComponentsImportance.Composer};\n flex-grow: 1;\n max-height: inherit;\n .o-composer {\n caret-color: black;\n padding-left: 3px;\n padding-right: 3px;\n word-break: break-all;\n &:focus {\n outline: none;\n }\n &.unfocusable {\n pointer-events: none;\n }\n span {\n white-space: pre;\n &.${SelectionIndicatorClass}:after {\n content: \"${SelectionIndicator}\";\n color: ${SelectionIndicatorColor};\n }\n }\n }\n .o-composer-assistant {\n position: absolute;\n margin: 4px;\n pointer-events: none;\n }\n\n .o-autocomplete-dropdown,\n .o-formula-assistant-container {\n box-shadow: 0 1px 4px 3px rgba(60, 64, 67, 0.15);\n }\n }\n\n /* Custom css to highlight topbar composer on focus */\n .o-topbar-toolbar .o-composer-container:focus-within {\n border: 1px solid ${SELECTION_BORDER_COLOR};\n }\n`;\n class Composer extends owl.Component {\n constructor() {\n super(...arguments);\n this.composerRef = owl.useRef(\"o_composer\");\n this.contentHelper = new ContentEditableHelper(this.composerRef.el);\n this.composerState = owl.useState({\n positionStart: 0,\n positionEnd: 0,\n });\n this.autoCompleteState = owl.useState({\n showProvider: false,\n values: [],\n selectedIndex: 0,\n });\n this.functionDescriptionState = owl.useState({\n showDescription: false,\n functionName: \"\",\n functionDescription: {},\n argToFocus: 0,\n });\n this.isKeyStillDown = false;\n this.compositionActive = false;\n // we can't allow input events to be triggered while we remove and add back the content of the composer in processContent\n this.shouldProcessInputEvents = false;\n this.tokens = [];\n this.keyMapping = {\n ArrowUp: this.processArrowKeys,\n ArrowDown: this.processArrowKeys,\n ArrowLeft: this.processArrowKeys,\n ArrowRight: this.processArrowKeys,\n Enter: this.processEnterKey,\n Escape: this.processEscapeKey,\n F2: () => console.warn(\"Not implemented\"),\n F4: this.processF4Key,\n Tab: (ev) => this.processTabKey(ev),\n };\n }\n get assistantStyle() {\n if (this.props.delimitation && this.props.rect) {\n const { x: cellX, y: cellY, height: cellHeight } = this.props.rect;\n const remainingHeight = this.props.delimitation.height - (cellY + cellHeight);\n let assistantStyle = \"\";\n if (cellY > remainingHeight) {\n // render top\n assistantStyle += `\n top: -8px;\n transform: translate(0, -100%);\n `;\n }\n if (cellX + ASSISTANT_WIDTH > this.props.delimitation.width) {\n // render left\n assistantStyle += `right:0px;`;\n }\n return (assistantStyle += `width:${ASSISTANT_WIDTH}px;`);\n }\n return `width:${ASSISTANT_WIDTH}px;`;\n }\n setup() {\n owl.onMounted(() => {\n const el = this.composerRef.el;\n this.contentHelper.updateEl(el);\n this.processContent();\n });\n owl.onWillUnmount(() => {\n var _a, _b;\n (_b = (_a = this.props).onComposerUnmounted) === null || _b === void 0 ? void 0 : _b.call(_a);\n });\n owl.onPatched(() => {\n if (!this.isKeyStillDown) {\n this.processContent();\n }\n });\n }\n // ---------------------------------------------------------------------------\n // Handlers\n // ---------------------------------------------------------------------------\n processArrowKeys(ev) {\n if (this.env.model.getters.isSelectingForComposer()) {\n this.functionDescriptionState.showDescription = false;\n // Prevent the default content editable behavior which moves the cursor\n ev.preventDefault();\n ev.stopPropagation();\n updateSelectionWithArrowKeys(ev, this.env.model.selection);\n return;\n }\n const content = this.env.model.getters.getCurrentContent();\n if (this.props.focus === \"cellFocus\" &&\n !this.autoCompleteState.showProvider &&\n !content.startsWith(\"=\")) {\n this.env.model.dispatch(\"STOP_EDITION\");\n return;\n }\n // All arrow keys are processed: up and down should move autocomplete, left\n // and right should move the cursor.\n ev.stopPropagation();\n this.handleArrowKeysForAutocomplete(ev);\n }\n handleArrowKeysForAutocomplete(ev) {\n // only for arrow up and down\n if ([\"ArrowUp\", \"ArrowDown\"].includes(ev.key) && this.autoCompleteState.showProvider) {\n ev.preventDefault();\n if (ev.key === \"ArrowUp\") {\n this.autoCompleteState.selectedIndex--;\n if (this.autoCompleteState.selectedIndex < 0) {\n this.autoCompleteState.selectedIndex = this.autoCompleteState.values.length - 1;\n }\n }\n else {\n this.autoCompleteState.selectedIndex =\n (this.autoCompleteState.selectedIndex + 1) % this.autoCompleteState.values.length;\n }\n }\n }\n processTabKey(ev) {\n var _a;\n ev.preventDefault();\n ev.stopPropagation();\n if (this.autoCompleteState.showProvider) {\n const autoCompleteValue = (_a = this.autoCompleteState.values[this.autoCompleteState.selectedIndex]) === null || _a === void 0 ? void 0 : _a.text;\n if (autoCompleteValue) {\n this.autoComplete(autoCompleteValue);\n return;\n }\n }\n else {\n // when completing with tab, if there is no value to complete, the active cell will be moved to the right.\n // we can't let the model think that it is for a ref selection.\n // todo: check if this can be removed someday\n this.env.model.dispatch(\"STOP_COMPOSER_RANGE_SELECTION\");\n }\n const direction = ev.shiftKey ? \"left\" : \"right\";\n this.env.model.dispatch(\"STOP_EDITION\");\n this.env.model.selection.moveAnchorCell(direction, 1);\n }\n processEnterKey(ev) {\n var _a;\n ev.preventDefault();\n ev.stopPropagation();\n this.isKeyStillDown = false;\n if (this.autoCompleteState.showProvider) {\n const autoCompleteValue = (_a = this.autoCompleteState.values[this.autoCompleteState.selectedIndex]) === null || _a === void 0 ? void 0 : _a.text;\n if (autoCompleteValue) {\n this.autoComplete(autoCompleteValue);\n return;\n }\n }\n this.env.model.dispatch(\"STOP_EDITION\");\n const direction = ev.shiftKey ? \"up\" : \"down\";\n this.env.model.selection.moveAnchorCell(direction, 1);\n }\n processEscapeKey() {\n this.env.model.dispatch(\"STOP_EDITION\", { cancel: true });\n }\n processF4Key() {\n this.env.model.dispatch(\"CYCLE_EDITION_REFERENCES\");\n this.processContent();\n }\n onCompositionStart() {\n this.compositionActive = true;\n }\n onCompositionEnd() {\n this.compositionActive = false;\n }\n onKeydown(ev) {\n let handler = this.keyMapping[ev.key];\n if (handler) {\n handler.call(this, ev);\n }\n else {\n ev.stopPropagation();\n }\n const { start, end } = this.contentHelper.getCurrentSelection();\n if (!this.env.model.getters.isSelectingForComposer()) {\n this.env.model.dispatch(\"CHANGE_COMPOSER_CURSOR_SELECTION\", { start, end });\n this.isKeyStillDown = true;\n }\n }\n /*\n * Triggered automatically by the content-editable between the keydown and key up\n * */\n onInput() {\n if (this.props.focus === \"inactive\" || !this.shouldProcessInputEvents) {\n return;\n }\n this.env.model.dispatch(\"STOP_COMPOSER_RANGE_SELECTION\");\n const el = this.composerRef.el;\n this.env.model.dispatch(\"SET_CURRENT_CONTENT\", {\n content: el.childNodes.length ? el.textContent : \"\",\n selection: this.contentHelper.getCurrentSelection(),\n });\n }\n onKeyup(ev) {\n this.isKeyStillDown = false;\n if (this.props.focus === \"inactive\" ||\n [\"Control\", \"Shift\", \"Tab\", \"Enter\", \"F4\"].includes(ev.key)) {\n return;\n }\n if (this.autoCompleteState.showProvider && [\"ArrowUp\", \"ArrowDown\"].includes(ev.key)) {\n return; // already processed in keydown\n }\n if (this.env.model.getters.isSelectingForComposer() &&\n [\"ArrowUp\", \"ArrowDown\", \"ArrowLeft\", \"ArrowRight\"].includes(ev.key)) {\n return; // already processed in keydown\n }\n ev.preventDefault();\n ev.stopPropagation();\n this.autoCompleteState.showProvider = false;\n if (ev.ctrlKey && ev.key === \" \") {\n this.showAutocomplete(\"\");\n this.env.model.dispatch(\"STOP_COMPOSER_RANGE_SELECTION\");\n return;\n }\n const { start: oldStart, end: oldEnd } = this.env.model.getters.getComposerSelection();\n const { start, end } = this.contentHelper.getCurrentSelection();\n if (start !== oldStart || end !== oldEnd) {\n this.env.model.dispatch(\"CHANGE_COMPOSER_CURSOR_SELECTION\", this.contentHelper.getCurrentSelection());\n }\n this.processTokenAtCursor();\n this.processContent();\n }\n showAutocomplete(searchTerm) {\n this.autoCompleteState.showProvider = true;\n let values = Object.entries(functionRegistry.content).map(([text, { description }]) => {\n return {\n text,\n description,\n };\n });\n values = values\n .filter((t) => t.text.toUpperCase().startsWith(searchTerm.toUpperCase()))\n .sort((l, r) => (l.text < r.text ? -1 : l.text > r.text ? 1 : 0));\n this.autoCompleteState.values = values.slice(0, 10);\n this.autoCompleteState.selectedIndex = 0;\n }\n onMousedown(ev) {\n if (ev.button > 0) {\n // not main button, probably a context menu\n return;\n }\n this.contentHelper.removeSelection();\n }\n onClick() {\n if (this.env.model.getters.isReadonly()) {\n return;\n }\n const newSelection = this.contentHelper.getCurrentSelection();\n this.env.model.dispatch(\"STOP_COMPOSER_RANGE_SELECTION\");\n if (this.props.focus === \"inactive\") {\n this.props.onComposerContentFocused(newSelection);\n }\n this.env.model.dispatch(\"CHANGE_COMPOSER_CURSOR_SELECTION\", newSelection);\n this.processTokenAtCursor();\n }\n onBlur() {\n this.isKeyStillDown = false;\n }\n // ---------------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------------\n processContent() {\n if (this.compositionActive) {\n return;\n }\n this.contentHelper.removeAll(); // removes the content of the composer, to be added just after\n this.shouldProcessInputEvents = false;\n if (this.props.focus !== \"inactive\") {\n this.contentHelper.selectRange(0, 0); // move the cursor inside the composer at 0 0.\n }\n const content = this.getContent();\n if (content.length !== 0) {\n this.contentHelper.setText(content);\n const { start, end } = this.env.model.getters.getComposerSelection();\n if (this.props.focus !== \"inactive\") {\n // Put the cursor back where it was before the rendering\n this.contentHelper.selectRange(start, end);\n }\n }\n this.shouldProcessInputEvents = true;\n }\n getContent() {\n let content;\n const value = this.env.model.getters.getCurrentContent();\n const isValidFormula = value.startsWith(\"=\") && this.env.model.getters.getCurrentTokens().length > 0;\n if (value === \"\") {\n content = [];\n }\n else if (isValidFormula && this.props.focus !== \"inactive\") {\n content = this.getColoredTokens();\n }\n else {\n content = [{ value }];\n }\n return content;\n }\n getColoredTokens() {\n const tokens = this.env.model.getters.getCurrentTokens();\n const tokenAtCursor = this.env.model.getters.getTokenAtCursor();\n const result = [];\n const { start, end } = this.env.model.getters.getComposerSelection();\n for (const token of tokens) {\n switch (token.type) {\n case \"OPERATOR\":\n case \"NUMBER\":\n case \"FUNCTION\":\n case \"COMMA\":\n case \"STRING\":\n result.push({ value: token.value, color: tokenColor[token.type] || \"#000\" });\n break;\n case \"REFERENCE\":\n const { xc, sheetName } = splitReference(token.value);\n result.push({ value: token.value, color: this.rangeColor(xc, sheetName) || \"#000\" });\n break;\n case \"SYMBOL\":\n let value = token.value;\n if ([\"TRUE\", \"FALSE\"].includes(value.toUpperCase())) {\n result.push({ value: token.value, color: NumberColor });\n }\n else {\n result.push({ value: token.value, color: \"#000\" });\n }\n break;\n case \"LEFT_PAREN\":\n case \"RIGHT_PAREN\":\n // Compute the matching parenthesis\n if (tokenAtCursor &&\n [\"LEFT_PAREN\", \"RIGHT_PAREN\"].includes(tokenAtCursor.type) &&\n tokenAtCursor.parenIndex &&\n tokenAtCursor.parenIndex === token.parenIndex) {\n result.push({ value: token.value, color: MatchingParenColor });\n }\n else {\n result.push({ value: token.value, color: tokenColor[token.type] || \"#000\" });\n }\n break;\n default:\n result.push({ value: token.value, color: \"#000\" });\n break;\n }\n if (this.env.model.getters.showSelectionIndicator() && end === start && end === token.end) {\n result[result.length - 1].class = SelectionIndicatorClass;\n }\n }\n return result;\n }\n rangeColor(xc, sheetName) {\n if (this.props.focus === \"inactive\") {\n return undefined;\n }\n const highlights = this.env.model.getters.getHighlights();\n const refSheet = sheetName\n ? this.env.model.getters.getSheetIdByName(sheetName)\n : this.env.model.getters.getEditionSheet();\n const highlight = highlights.find((highlight) => {\n if (highlight.sheetId !== refSheet)\n return false;\n const range = this.env.model.getters.getRangeFromSheetXC(refSheet, xc);\n let zone = range.zone;\n const { height, width } = zoneToDimension(zone);\n zone = height * width === 1 ? this.env.model.getters.expandZone(refSheet, zone) : zone;\n return isEqual(zone, highlight.zone);\n });\n return highlight && highlight.color ? highlight.color : undefined;\n }\n /**\n * Compute the state of the composer from the tokenAtCursor.\n * If the token is a function or symbol (that isn't a cell/range reference) we have to initialize\n * the autocomplete engine otherwise we initialize the formula assistant.\n */\n processTokenAtCursor() {\n let content = this.env.model.getters.getCurrentContent();\n this.autoCompleteState.showProvider = false;\n this.functionDescriptionState.showDescription = false;\n if (content.startsWith(\"=\")) {\n const tokenAtCursor = this.env.model.getters.getTokenAtCursor();\n if (tokenAtCursor) {\n const { xc } = splitReference(tokenAtCursor.value);\n if (tokenAtCursor.type === \"FUNCTION\" ||\n (tokenAtCursor.type === \"SYMBOL\" && !rangeReference.test(xc))) {\n // initialize Autocomplete Dropdown\n this.showAutocomplete(tokenAtCursor.value);\n }\n else if (tokenAtCursor.functionContext && tokenAtCursor.type !== \"UNKNOWN\") {\n // initialize Formula Assistant\n const tokenContext = tokenAtCursor.functionContext;\n const parentFunction = tokenContext.parent.toUpperCase();\n const description = functions[parentFunction];\n const argPosition = tokenContext.argPosition;\n this.functionDescriptionState.functionName = parentFunction;\n this.functionDescriptionState.functionDescription = description;\n this.functionDescriptionState.argToFocus = description.getArgToFocus(argPosition + 1) - 1;\n this.functionDescriptionState.showDescription = true;\n }\n }\n }\n }\n autoComplete(value) {\n if (value) {\n const tokenAtCursor = this.env.model.getters.getTokenAtCursor();\n if (tokenAtCursor) {\n let start = tokenAtCursor.end;\n let end = tokenAtCursor.end;\n // shouldn't it be REFERENCE ?\n if ([\"SYMBOL\", \"FUNCTION\"].includes(tokenAtCursor.type)) {\n start = tokenAtCursor.start;\n }\n const tokens = this.env.model.getters.getCurrentTokens();\n if (tokens.length) {\n value += \"(\";\n const currentTokenIndex = tokens.map((token) => token.start).indexOf(tokenAtCursor.start);\n if (currentTokenIndex + 1 < tokens.length) {\n const nextToken = tokens[currentTokenIndex + 1];\n if (nextToken.type === \"LEFT_PAREN\") {\n end++;\n }\n }\n }\n this.env.model.dispatch(\"CHANGE_COMPOSER_CURSOR_SELECTION\", {\n start,\n end,\n });\n }\n this.env.model.dispatch(\"REPLACE_COMPOSER_CURSOR_SELECTION\", {\n text: value,\n });\n }\n this.processTokenAtCursor();\n }\n }\n Composer.template = \"o-spreadsheet-Composer\";\n Composer.components = { TextValueProvider, FunctionDescriptionProvider };\n Composer.defaultProps = {\n inputStyle: \"\",\n };\n Composer.props = {\n inputStyle: { type: String, optional: true },\n rect: { type: Object, optional: true },\n delimitation: { type: Object, optional: true },\n focus: { validate: (value) => [\"inactive\", \"cellFocus\", \"contentFocus\"].includes(value) },\n onComposerUnmounted: { type: Function, optional: true },\n onComposerContentFocused: Function,\n };\n\n const SCROLLBAR_WIDTH = 14;\n const SCROLLBAR_HIGHT = 15;\n const COMPOSER_BORDER_WIDTH = 3 * 0.4 * window.devicePixelRatio || 1;\n css /* scss */ `\n div.o-grid-composer {\n z-index: ${ComponentsImportance.Composer};\n box-sizing: border-box;\n position: absolute;\n border: ${COMPOSER_BORDER_WIDTH}px solid ${SELECTION_BORDER_COLOR};\n }\n`;\n /**\n * This component is a composer which positions itself on the grid at the anchor cell.\n * It also applies the style of the cell to the composer input.\n */\n class GridComposer extends owl.Component {\n setup() {\n this.gridComposerRef = owl.useRef(\"gridComposer\");\n this.composerState = owl.useState({\n rect: undefined,\n delimitation: undefined,\n });\n const { sheetId, col, row } = this.env.model.getters.getActivePosition();\n this.zone = this.env.model.getters.expandZone(sheetId, positionToZone({ col, row }));\n this.rect = this.env.model.getters.getVisibleRect(this.zone);\n owl.onMounted(() => {\n const el = this.gridComposerRef.el;\n //TODO Should be more correct to have a props that give the parent's clientHeight and clientWidth\n const maxHeight = el.parentElement.clientHeight - this.rect.y - SCROLLBAR_HIGHT;\n el.style.maxHeight = (maxHeight + \"px\");\n const maxWidth = el.parentElement.clientWidth - this.rect.x - SCROLLBAR_WIDTH;\n el.style.maxWidth = (maxWidth + \"px\");\n this.composerState.rect = {\n x: this.rect.x,\n y: this.rect.y,\n width: el.clientWidth,\n height: el.clientHeight,\n };\n this.composerState.delimitation = {\n width: el.parentElement.clientWidth,\n height: el.parentElement.clientHeight,\n };\n });\n }\n get containerStyle() {\n const isFormula = this.env.model.getters.getCurrentContent().startsWith(\"=\");\n const cell = this.env.model.getters.getActiveCell();\n const position = this.env.model.getters.getActivePosition();\n const style = this.env.model.getters.getCellComputedStyle(position);\n // position style\n const { x: left, y: top, width, height } = this.rect;\n // color style\n const background = (!isFormula && style.fillColor) || \"#ffffff\";\n const color = (!isFormula && style.textColor) || \"#000000\";\n // font style\n const fontSize = (!isFormula && style.fontSize) || 10;\n const fontWeight = !isFormula && style.bold ? \"bold\" : 500;\n const fontStyle = !isFormula && style.italic ? \"italic\" : \"normal\";\n const textDecoration = !isFormula ? getTextDecoration(style) : \"none\";\n // align style\n let textAlign = \"left\";\n if (!isFormula) {\n textAlign = style.align || cell.defaultAlign;\n }\n return `\n left: ${left - 1}px;\n top: ${top}px;\n min-width: ${width + 2}px;\n min-height: ${height + 1}px;\n\n background: ${background};\n color: ${color};\n\n font-size: ${fontSizeMap[fontSize]}px;\n font-weight: ${fontWeight};\n font-style: ${fontStyle};\n text-decoration: ${textDecoration};\n\n text-align: ${textAlign};\n `;\n }\n get composerStyle() {\n return `\n line-height: ${DEFAULT_CELL_HEIGHT}px;\n max-height: inherit;\n overflow: hidden;\n `;\n }\n }\n GridComposer.template = \"o-spreadsheet-GridComposer\";\n GridComposer.components = { Composer };\n GridComposer.props = {\n focus: { validate: (value) => [\"inactive\", \"cellFocus\", \"contentFocus\"].includes(value) },\n onComposerUnmounted: Function,\n onComposerContentFocused: Function,\n };\n\n const CSS$2 = css /* scss */ `\n .o-filter-icon {\n color: ${FILTERS_COLOR};\n position: absolute;\n display: flex;\n align-items: center;\n justify-content: center;\n width: ${FILTER_ICON_EDGE_LENGTH}px;\n height: ${FILTER_ICON_EDGE_LENGTH}px;\n\n svg {\n path {\n fill: ${FILTERS_COLOR};\n }\n }\n }\n .o-filter-icon:hover {\n background: ${FILTERS_COLOR};\n svg {\n path {\n fill: white;\n }\n }\n }\n`;\n class FilterIcon extends owl.Component {\n get style() {\n const { x, y } = this.props.position;\n return `top:${y}px;left:${x}px`;\n }\n }\n FilterIcon.style = CSS$2;\n FilterIcon.template = \"o-spreadsheet-FilterIcon\";\n FilterIcon.props = {\n position: Object,\n isActive: Boolean,\n onClick: Function,\n };\n\n const CSS$1 = css /* scss */ ``;\n class FilterIconsOverlay extends owl.Component {\n getVisibleFilterHeaders() {\n const sheetId = this.env.model.getters.getActiveSheetId();\n const headerPositions = this.env.model.getters.getFilterHeaders(sheetId);\n return headerPositions.filter((position) => this.isPositionVisible(position.col, position.row));\n }\n getFilterHeaderPosition(position) {\n const sheetId = this.env.model.getters.getActiveSheetId();\n const rowDims = this.env.model.getters.getRowDimensionsInViewport(sheetId, position.row);\n const colDims = this.env.model.getters.getColDimensionsInViewport(sheetId, position.col);\n // TODO : change this offset when we support vertical cell align\n const centeringOffset = Math.floor((rowDims.size - FILTER_ICON_EDGE_LENGTH) / 2);\n return {\n x: colDims.end - FILTER_ICON_EDGE_LENGTH + this.props.gridPosition.x - FILTER_ICON_MARGIN - 1,\n y: rowDims.end - FILTER_ICON_EDGE_LENGTH + this.props.gridPosition.y - centeringOffset,\n };\n }\n isFilterActive(position) {\n const sheetId = this.env.model.getters.getActiveSheetId();\n return this.env.model.getters.isFilterActive({ sheetId, ...position });\n }\n toggleFilterMenu(position) {\n const activePopoverType = this.env.model.getters.getPersistentPopoverTypeAtPosition(position);\n if (activePopoverType && activePopoverType === \"FilterMenu\") {\n this.env.model.dispatch(\"CLOSE_CELL_POPOVER\");\n return;\n }\n const { col, row } = position;\n this.env.model.dispatch(\"OPEN_CELL_POPOVER\", {\n col,\n row,\n popoverType: \"FilterMenu\",\n });\n }\n isPositionVisible(x, y) {\n const rect = this.env.model.getters.getVisibleRect({\n left: x,\n right: x,\n top: y,\n bottom: y,\n });\n return !(rect.width === 0 || rect.height === 0);\n }\n }\n FilterIconsOverlay.style = CSS$1;\n FilterIconsOverlay.template = \"o-spreadsheet-FilterIconsOverlay\";\n FilterIconsOverlay.components = {\n FilterIcon,\n };\n FilterIconsOverlay.defaultProps = {\n gridPosition: { x: 0, y: 0 },\n };\n FilterIconsOverlay.props = {\n gridPosition: { type: Object, optional: true },\n };\n\n /**\n * Add the `https` prefix to the url if it's missing\n */\n function withHttps(url) {\n return !/^https?:\\/\\//i.test(url) ? `https://${url}` : url;\n }\n const urlRegistry = new Registry();\n function createWebLink(url, label) {\n url = withHttps(url);\n return {\n url,\n label: label || url,\n isExternal: true,\n isUrlEditable: true,\n };\n }\n urlRegistry.add(\"sheet_URL\", {\n match: (url) => isSheetUrl(url),\n createLink: (url, label) => {\n return {\n label,\n url,\n isExternal: false,\n isUrlEditable: false,\n };\n },\n urlRepresentation(url, getters) {\n const sheetId = parseSheetUrl(url);\n return getters.tryGetSheetName(sheetId) || _lt(\"Invalid sheet\");\n },\n open(url, env) {\n const sheetId = parseSheetUrl(url);\n env.model.dispatch(\"ACTIVATE_SHEET\", {\n sheetIdFrom: env.model.getters.getActiveSheetId(),\n sheetIdTo: sheetId,\n });\n },\n sequence: 0,\n });\n const WebUrlSpec = {\n createLink: createWebLink,\n match: (url) => isWebLink(url),\n open: (url) => window.open(url, \"_blank\"),\n urlRepresentation: (url) => url,\n sequence: 0,\n };\n function findMatchingSpec(url) {\n return (urlRegistry\n .getAll()\n .sort((a, b) => a.sequence - b.sequence)\n .find((urlType) => urlType.match(url)) || WebUrlSpec);\n }\n function urlRepresentation(link, getters) {\n return findMatchingSpec(link.url).urlRepresentation(link.url, getters);\n }\n function openLink(link, env) {\n findMatchingSpec(link.url).open(link.url, env);\n }\n function detectLink(value) {\n if (typeof value !== \"string\") {\n return undefined;\n }\n if (isMarkdownLink(value)) {\n const { label, url } = parseMarkdownLink(value);\n return findMatchingSpec(url).createLink(url, label);\n }\n else if (isWebLink(value)) {\n return createWebLink(value);\n }\n return undefined;\n }\n\n function evaluateLiteral(content, format) {\n return createEvaluatedCell(parseLiteral(content || \"\"), format);\n }\n function parseLiteral(content) {\n if (content.startsWith(\"=\")) {\n throw new Error(`Cannot parse \"${content}\" because it's not a literal value. It's a formula`);\n }\n if (isNumber(content) || isDateTime(content)) {\n return toNumber(content);\n }\n else if (isBoolean(content)) {\n return content.toUpperCase() === \"TRUE\" ? true : false;\n }\n return content;\n }\n function createEvaluatedCell(value, format) {\n const link = detectLink(value);\n if (link) {\n return {\n ..._createEvaluatedCell(parseLiteral(link.label), format || detectFormat(link.label)),\n link,\n };\n }\n return _createEvaluatedCell(value, format);\n }\n function _createEvaluatedCell(value, format) {\n try {\n for (const builder of builders) {\n const evaluateCell = builder(value, format);\n if (evaluateCell) {\n return evaluateCell;\n }\n }\n return textCell((value || \"\").toString(), format);\n }\n catch (error) {\n return errorCell((value || \"\").toString(), new EvaluationError(CellErrorType.GenericError, error.message || DEFAULT_ERROR_MESSAGE));\n }\n }\n function textCell(value, format) {\n return {\n type: CellValueType.text,\n value,\n format,\n isAutoSummable: true,\n defaultAlign: \"left\",\n formattedValue: formatValue(value, format),\n };\n }\n function numberCell(value, format) {\n return {\n type: CellValueType.number,\n value: value || 0,\n format,\n isAutoSummable: true,\n defaultAlign: \"right\",\n formattedValue: formatValue(value, format),\n };\n }\n const EMPTY_EVALUATED_CELL = {\n type: CellValueType.empty,\n value: \"\",\n format: undefined,\n isAutoSummable: true,\n defaultAlign: \"left\",\n formattedValue: \"\",\n };\n function emptyCell(format) {\n if (format === undefined) {\n // share the same object to save memory\n return EMPTY_EVALUATED_CELL;\n }\n return {\n type: CellValueType.empty,\n value: \"\",\n format,\n isAutoSummable: true,\n defaultAlign: \"left\",\n formattedValue: \"\",\n };\n }\n function dateTimeCell(value, format) {\n const formattedValue = formatValue(value, format);\n return {\n type: CellValueType.number,\n value,\n format,\n isAutoSummable: false,\n defaultAlign: \"right\",\n formattedValue,\n };\n }\n function booleanCell(value, format) {\n const formattedValue = value ? \"TRUE\" : \"FALSE\";\n return {\n type: CellValueType.boolean,\n value,\n format,\n isAutoSummable: false,\n defaultAlign: \"center\",\n formattedValue,\n };\n }\n function errorCell(content, error) {\n return {\n type: CellValueType.error,\n value: error.errorType,\n error,\n isAutoSummable: false,\n defaultAlign: \"center\",\n formattedValue: error.errorType,\n };\n }\n const builders = [\n function createEmpty(value, format) {\n if (value === \"\") {\n return emptyCell(format);\n }\n return undefined;\n },\n function createDateTime(value, format) {\n if (!!format && typeof value === \"number\" && isDateTimeFormat(format)) {\n return dateTimeCell(value, format);\n }\n return undefined;\n },\n function createNumber(value, format) {\n if (typeof value === \"number\") {\n return numberCell(value, format);\n }\n else if (value === null) {\n return numberCell(0, format);\n }\n return undefined;\n },\n function createBoolean(value, format) {\n if (typeof value === \"boolean\") {\n return booleanCell(value, format);\n }\n return undefined;\n },\n ];\n\n /**\n * An AutofillModifierImplementation is used to describe how to handle a\n * AutofillModifier.\n */\n const autofillModifiersRegistry = new Registry();\n autofillModifiersRegistry\n .add(\"INCREMENT_MODIFIER\", {\n apply: (rule, data) => {\n var _a;\n rule.current += rule.increment;\n const content = rule.current.toString();\n const tooltipValue = formatValue(rule.current, (_a = data.cell) === null || _a === void 0 ? void 0 : _a.format);\n return {\n cellData: {\n border: data.border,\n style: data.cell && data.cell.style,\n format: data.cell && data.cell.format,\n content,\n },\n tooltip: content ? { props: { content: tooltipValue } } : undefined,\n };\n },\n })\n .add(\"COPY_MODIFIER\", {\n apply: (rule, data, getters) => {\n var _a, _b, _c;\n const content = ((_a = data.cell) === null || _a === void 0 ? void 0 : _a.content) || \"\";\n return {\n cellData: {\n border: data.border,\n style: data.cell && data.cell.style,\n format: data.cell && data.cell.format,\n content,\n },\n tooltip: content\n ? {\n props: {\n content: evaluateLiteral((_b = data.cell) === null || _b === void 0 ? void 0 : _b.content, (_c = data.cell) === null || _c === void 0 ? void 0 : _c.format).formattedValue,\n },\n }\n : undefined,\n };\n },\n })\n .add(\"FORMULA_MODIFIER\", {\n apply: (rule, data, getters, direction) => {\n rule.current += rule.increment;\n let x = 0;\n let y = 0;\n switch (direction) {\n case \"up\" /* DIRECTION.UP */:\n x = 0;\n y = -rule.current;\n break;\n case \"down\" /* DIRECTION.DOWN */:\n x = 0;\n y = rule.current;\n break;\n case \"left\" /* DIRECTION.LEFT */:\n x = -rule.current;\n y = 0;\n break;\n case \"right\" /* DIRECTION.RIGHT */:\n x = rule.current;\n y = 0;\n break;\n }\n const cell = data.cell;\n if (!cell || !cell.isFormula) {\n return { cellData: {} };\n }\n const sheetId = data.sheetId;\n const ranges = getters.createAdaptedRanges(cell.dependencies, x, y, sheetId);\n const content = getters.buildFormulaContent(sheetId, cell, ranges);\n return {\n cellData: {\n border: data.border,\n style: cell.style,\n format: cell.format,\n content,\n },\n tooltip: content ? { props: { content } } : undefined,\n };\n },\n });\n\n const autofillRulesRegistry = new Registry();\n /**\n * Get the consecutive xc that are of type \"number\" or \"date\".\n * Return the one which contains the given cell\n */\n function getGroup(cell, cells) {\n let group = [];\n let found = false;\n for (let x of cells) {\n if (x === cell) {\n found = true;\n }\n const cellValue = evaluateLiteral(x === null || x === void 0 ? void 0 : x.content);\n if (cellValue.type === CellValueType.number) {\n group.push(cellValue.value);\n }\n else {\n if (found) {\n return group;\n }\n group = [];\n }\n }\n return group;\n }\n /**\n * Get the average steps between numbers\n */\n function getAverageIncrement(group) {\n const averages = [];\n let last = group[0];\n for (let i = 1; i < group.length; i++) {\n const current = group[i];\n averages.push(current - last);\n last = current;\n }\n return averages.reduce((a, b) => a + b, 0) / averages.length;\n }\n autofillRulesRegistry\n .add(\"simple_value_copy\", {\n condition: (cell, cells) => {\n var _a;\n return cells.length === 1 && !cell.isFormula && !((_a = cell.format) === null || _a === void 0 ? void 0 : _a.match(DATETIME_FORMAT));\n },\n generateRule: () => {\n return { type: \"COPY_MODIFIER\" };\n },\n sequence: 10,\n })\n .add(\"copy_text\", {\n condition: (cell) => !cell.isFormula && evaluateLiteral(cell.content).type === CellValueType.text,\n generateRule: () => {\n return { type: \"COPY_MODIFIER\" };\n },\n sequence: 20,\n })\n .add(\"update_formula\", {\n condition: (cell) => cell.isFormula,\n generateRule: (_, cells) => {\n return { type: \"FORMULA_MODIFIER\", increment: cells.length, current: 0 };\n },\n sequence: 30,\n })\n .add(\"increment_number\", {\n condition: (cell) => !cell.isFormula && evaluateLiteral(cell.content).type === CellValueType.number,\n generateRule: (cell, cells) => {\n const group = getGroup(cell, cells);\n let increment = 1;\n if (group.length == 2) {\n increment = (group[1] - group[0]) * 2;\n }\n else if (group.length > 2) {\n increment = getAverageIncrement(group) * group.length;\n }\n const evaluation = evaluateLiteral(cell.content);\n return {\n type: \"INCREMENT_MODIFIER\",\n increment,\n current: evaluation.type === CellValueType.number ? evaluation.value : 0,\n };\n },\n sequence: 40,\n });\n\n const ERROR_TOOLTIP_MAX_HEIGHT = 80;\n const ERROR_TOOLTIP_WIDTH = 180;\n css /* scss */ `\n .o-error-tooltip {\n font-size: 13px;\n background-color: white;\n border-left: 3px solid red;\n padding: 10px;\n width: ${ERROR_TOOLTIP_WIDTH}px;\n box-sizing: border-box !important;\n }\n`;\n class ErrorToolTip extends owl.Component {\n }\n ErrorToolTip.maxSize = { maxHeight: ERROR_TOOLTIP_MAX_HEIGHT };\n ErrorToolTip.template = \"o-spreadsheet-ErrorToolTip\";\n ErrorToolTip.components = {};\n ErrorToolTip.props = {\n text: String,\n onClosed: { type: Function, optional: true },\n };\n const ErrorToolTipPopoverBuilder = {\n onHover: (position, getters) => {\n const cell = getters.getEvaluatedCell(position);\n if (cell.type === CellValueType.error && cell.error.logLevel > CellErrorLevel.silent) {\n return {\n isOpen: true,\n props: { text: cell.error.message },\n Component: ErrorToolTip,\n cellCorner: \"TopRight\",\n };\n }\n return { isOpen: false };\n },\n };\n\n class FilterMenuValueItem extends owl.Component {\n constructor() {\n super(...arguments);\n this.itemRef = owl.useRef(\"menuValueItem\");\n }\n setup() {\n owl.onWillPatch(() => {\n if (this.props.scrolledTo) {\n this.scrollListToSelectedValue();\n }\n });\n }\n scrollListToSelectedValue() {\n var _a, _b;\n if (!this.itemRef.el) {\n return;\n }\n (_b = (_a = this.itemRef.el).scrollIntoView) === null || _b === void 0 ? void 0 : _b.call(_a, {\n block: this.props.scrolledTo === \"bottom\" ? \"end\" : \"start\",\n });\n }\n }\n FilterMenuValueItem.template = \"o-spreadsheet-FilterMenuValueItem\";\n FilterMenuValueItem.props = {\n value: String,\n isChecked: Boolean,\n isSelected: Boolean,\n onMouseMove: Function,\n onClick: Function,\n scrolledTo: { type: String, optional: true },\n };\n\n const FILTER_MENU_HEIGHT = 295;\n const CSS = css /* scss */ `\n .o-filter-menu {\n box-sizing: border-box;\n padding: 8px 16px;\n height: ${FILTER_MENU_HEIGHT}px;\n line-height: 1;\n\n .o-filter-menu-item {\n display: flex;\n box-sizing: border-box;\n height: ${MENU_ITEM_HEIGHT}px;\n padding: 4px 4px 4px 0px;\n cursor: pointer;\n user-select: none;\n\n &.selected {\n background-color: rgba(0, 0, 0, 0.08);\n }\n }\n\n input {\n box-sizing: border-box;\n margin-bottom: 5px;\n border: 1px solid #949494;\n height: 24px;\n padding-right: 28px;\n }\n\n .o-search-icon {\n right: 5px;\n top: 4px;\n\n svg {\n height: 16px;\n width: 16px;\n vertical-align: middle;\n }\n }\n\n .o-filter-menu-actions {\n display: flex;\n flex-direction: row;\n margin-bottom: 4px;\n\n .o-filter-menu-action-text {\n cursor: pointer;\n margin-right: 10px;\n color: blue;\n text-decoration: underline;\n }\n }\n\n .o-filter-menu-list {\n flex: auto;\n overflow-y: auto;\n border: 1px solid #949494;\n\n .o-filter-menu-value {\n padding: 4px;\n line-height: 20px;\n height: 28px;\n .o-filter-menu-value-checked {\n width: 20px;\n }\n }\n\n .o-filter-menu-no-values {\n color: #949494;\n font-style: italic;\n }\n }\n\n .o-filter-menu-buttons {\n margin-top: 9px;\n\n .o-filter-menu-button {\n border: 1px solid lightgrey;\n padding: 6px 10px;\n cursor: pointer;\n border-radius: 4px;\n font-weight: 500;\n line-height: 16px;\n }\n\n .o-filter-menu-button-cancel {\n background: white;\n &:hover {\n background-color: rgba(0, 0, 0, 0.08);\n }\n }\n\n .o-filter-menu-button-primary {\n background-color: #188038;\n &:hover {\n background-color: #1d9641;\n }\n color: white;\n font-weight: bold;\n margin-left: 10px;\n }\n }\n }\n`;\n class FilterMenu extends owl.Component {\n constructor() {\n super(...arguments);\n this.state = owl.useState({\n values: [],\n textFilter: \"\",\n selectedValue: undefined,\n });\n this.searchBar = owl.useRef(\"filterMenuSearchBar\");\n }\n setup() {\n owl.onWillUpdateProps((nextProps) => {\n if (!deepEquals(nextProps.filterPosition, this.props.filterPosition)) {\n this.state.values = this.getFilterValues(nextProps.filterPosition);\n }\n });\n this.state.values = this.getFilterValues(this.props.filterPosition);\n }\n getFilterValues(position) {\n const sheetId = this.env.model.getters.getActiveSheetId();\n const filter = this.env.model.getters.getFilter({ sheetId, ...position });\n if (!filter) {\n return [];\n }\n const cellValues = (filter.filteredZone ? positions(filter.filteredZone) : [])\n .filter(({ row }) => !this.env.model.getters.isRowHidden(sheetId, row))\n .map(({ col, row }) => this.env.model.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue);\n const filterValues = this.env.model.getters.getFilterValues({ sheetId, ...position });\n const strValues = [...cellValues, ...filterValues];\n const normalizedFilteredValues = filterValues.map(toLowerCase);\n // Set with lowercase values to avoid duplicates\n const normalizedValues = [...new Set(strValues.map(toLowerCase))];\n const sortedValues = normalizedValues.sort((val1, val2) => val1.localeCompare(val2, undefined, { numeric: true, sensitivity: \"base\" }));\n return sortedValues.map((normalizedValue) => {\n const checked = normalizedFilteredValues.findIndex((filteredValue) => filteredValue === normalizedValue) ===\n -1;\n return {\n checked,\n string: strValues.find((val) => toLowerCase(val) === normalizedValue) || \"\",\n };\n });\n }\n checkValue(value) {\n var _a;\n this.state.selectedValue = value.string;\n value.checked = !value.checked;\n (_a = this.searchBar.el) === null || _a === void 0 ? void 0 : _a.focus();\n }\n onMouseMove(value) {\n this.state.selectedValue = value.string;\n }\n selectAll() {\n this.state.values.forEach((value) => (value.checked = true));\n }\n clearAll() {\n this.state.values.forEach((value) => (value.checked = false));\n }\n get filterTable() {\n const sheetId = this.env.model.getters.getActiveSheetId();\n const position = this.props.filterPosition;\n return this.env.model.getters.getFilterTable({ sheetId, ...position });\n }\n get displayedValues() {\n if (!this.state.textFilter) {\n return this.state.values;\n }\n return fuzzyLookup(this.state.textFilter, this.state.values, (val) => val.string);\n }\n confirm() {\n var _a, _b;\n const position = this.props.filterPosition;\n this.env.model.dispatch(\"UPDATE_FILTER\", {\n ...position,\n sheetId: this.env.model.getters.getActiveSheetId(),\n values: this.state.values.filter((val) => !val.checked).map((val) => val.string),\n });\n (_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);\n }\n cancel() {\n var _a, _b;\n (_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);\n }\n onKeyDown(ev) {\n const displayedValues = this.displayedValues;\n if (displayedValues.length === 0)\n return;\n let selectedIndex = undefined;\n if (this.state.selectedValue !== undefined) {\n const index = displayedValues.findIndex((val) => val.string === this.state.selectedValue);\n selectedIndex = index === -1 ? undefined : index;\n }\n switch (ev.key) {\n case \"ArrowDown\":\n if (selectedIndex === undefined) {\n selectedIndex = 0;\n }\n else {\n selectedIndex = Math.min(selectedIndex + 1, displayedValues.length - 1);\n }\n ev.preventDefault();\n ev.stopPropagation();\n break;\n case \"ArrowUp\":\n if (selectedIndex === undefined) {\n selectedIndex = displayedValues.length - 1;\n }\n else {\n selectedIndex = Math.max(selectedIndex - 1, 0);\n }\n ev.preventDefault();\n ev.stopPropagation();\n break;\n case \"Enter\":\n if (selectedIndex !== undefined) {\n this.checkValue(displayedValues[selectedIndex]);\n }\n ev.stopPropagation();\n ev.preventDefault();\n break;\n }\n this.state.selectedValue =\n selectedIndex !== undefined ? displayedValues[selectedIndex].string : undefined;\n if (ev.key === \"ArrowUp\" || ev.key === \"ArrowDown\") {\n this.scrollListToSelectedValue(ev.key);\n }\n }\n clearScrolledToValue() {\n this.state.values.forEach((val) => (val.scrolledTo = undefined));\n }\n scrollListToSelectedValue(arrow) {\n this.clearScrolledToValue();\n const selectedValue = this.state.values.find((val) => val.string === this.state.selectedValue);\n if (selectedValue) {\n selectedValue.scrolledTo = arrow === \"ArrowUp\" ? \"top\" : \"bottom\";\n }\n }\n sortFilterZone(sortDirection) {\n var _a, _b;\n const filterPosition = this.props.filterPosition;\n const filterTable = this.filterTable;\n if (!filterPosition || !filterTable || !filterTable.contentZone) {\n return;\n }\n const sheetId = this.env.model.getters.getActiveSheetId();\n this.env.model.dispatch(\"SORT_CELLS\", {\n sheetId,\n col: filterPosition.col,\n row: filterTable.contentZone.top,\n zone: filterTable.contentZone,\n sortDirection,\n sortOptions: { emptyCellAsZero: true, sortHeaders: true },\n });\n (_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);\n }\n }\n FilterMenu.size = { width: MENU_WIDTH, height: FILTER_MENU_HEIGHT };\n FilterMenu.template = \"o-spreadsheet-FilterMenu\";\n FilterMenu.style = CSS;\n FilterMenu.components = { FilterMenuValueItem };\n FilterMenu.props = {\n filterPosition: Object,\n onClosed: { type: Function, optional: true },\n };\n const FilterMenuPopoverBuilder = {\n onOpen: (position, getters) => {\n return {\n isOpen: true,\n props: { filterPosition: position },\n Component: FilterMenu,\n cellCorner: \"BottomLeft\",\n };\n },\n };\n\n const LINK_TOOLTIP_HEIGHT = 32;\n const LINK_TOOLTIP_WIDTH = 220;\n css /* scss */ `\n .o-link-tool {\n font-size: 13px;\n background-color: white;\n box-shadow: 0 1px 4px 3px rgba(60, 64, 67, 0.15);\n padding: 6px 12px;\n border-radius: 4px;\n display: flex;\n justify-content: space-between;\n height: ${LINK_TOOLTIP_HEIGHT}px;\n width: ${LINK_TOOLTIP_WIDTH}px;\n box-sizing: border-box !important;\n\n img {\n margin-right: 3px;\n width: 16px;\n height: 16px;\n }\n\n a.o-link {\n color: #01666b;\n text-decoration: none;\n flex-grow: 2;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n a.o-link:hover {\n text-decoration: none;\n color: #001d1f;\n cursor: pointer;\n }\n }\n .o-link-icon {\n float: right;\n padding-left: 5px;\n .o-icon {\n height: 16px;\n }\n }\n .o-link-icon .o-icon {\n height: 13px;\n }\n .o-link-icon:hover {\n cursor: pointer;\n color: #000;\n }\n`;\n class LinkDisplay extends owl.Component {\n get cell() {\n const { col, row } = this.props.cellPosition;\n const sheetId = this.env.model.getters.getActiveSheetId();\n return this.env.model.getters.getEvaluatedCell({ sheetId, col, row });\n }\n get link() {\n if (this.cell.link) {\n return this.cell.link;\n }\n const { col, row } = this.props.cellPosition;\n throw new Error(`LinkDisplay Component can only be used with link cells. ${toXC(col, row)} is not a link.`);\n }\n getUrlRepresentation(link) {\n return urlRepresentation(link, this.env.model.getters);\n }\n openLink() {\n openLink(this.link, this.env);\n }\n edit() {\n const { col, row } = this.props.cellPosition;\n this.env.model.dispatch(\"OPEN_CELL_POPOVER\", {\n col,\n row,\n popoverType: \"LinkEditor\",\n });\n }\n unlink() {\n const sheetId = this.env.model.getters.getActiveSheetId();\n const { col, row } = this.props.cellPosition;\n const style = this.env.model.getters.getCellComputedStyle({ sheetId, col, row });\n const textColor = (style === null || style === void 0 ? void 0 : style.textColor) === LINK_COLOR ? undefined : style === null || style === void 0 ? void 0 : style.textColor;\n this.env.model.dispatch(\"UPDATE_CELL\", {\n col,\n row,\n sheetId,\n content: this.link.label,\n style: { ...style, textColor, underline: undefined },\n });\n }\n }\n LinkDisplay.components = { Menu };\n LinkDisplay.template = \"o-spreadsheet-LinkDisplay\";\n const LinkCellPopoverBuilder = {\n onHover: (position, getters) => {\n const cell = getters.getEvaluatedCell(position);\n const shouldDisplayLink = !getters.isDashboard() && cell.link && getters.isVisibleInViewport(position);\n if (!shouldDisplayLink)\n return { isOpen: false };\n return {\n isOpen: true,\n Component: LinkDisplay,\n props: { cellPosition: position },\n cellCorner: \"BottomLeft\",\n };\n },\n };\n LinkDisplay.props = {\n cellPosition: Object,\n onClosed: { type: Function, optional: true },\n };\n\n //------------------------------------------------------------------------------\n // Link Menu Registry\n //------------------------------------------------------------------------------\n const linkMenuRegistry = new MenuItemRegistry();\n linkMenuRegistry\n .add(\"sheet\", {\n name: _lt(\"Link sheet\"),\n sequence: 10,\n })\n .addChild(\"sheet_list\", [\"sheet\"], (env) => {\n const sheets = env.model.getters\n .getSheetIds()\n .map((sheetId) => env.model.getters.getSheet(sheetId));\n return sheets.map((sheet, i) => createFullMenuItem(sheet.id, {\n name: sheet.name,\n sequence: i,\n action: () => markdownLink(sheet.name, buildSheetLink(sheet.id)),\n }));\n });\n\n const MENU_OFFSET_X = 320;\n const MENU_OFFSET_Y = 100;\n const PADDING = 12;\n const LINK_EDITOR_WIDTH = 340;\n const LINK_EDITOR_HEIGHT = 165;\n css /* scss */ `\n .o-link-editor {\n font-size: 13px;\n background-color: white;\n box-shadow: 0 1px 4px 3px rgba(60, 64, 67, 0.15);\n padding: ${PADDING}px;\n display: flex;\n flex-direction: column;\n border-radius: 4px;\n height: ${LINK_EDITOR_HEIGHT}px;\n width: ${LINK_EDITOR_WIDTH}px;\n\n .o-section {\n .o-section-title {\n font-weight: bold;\n color: dimgrey;\n margin-bottom: 5px;\n }\n }\n .o-buttons {\n padding-left: 16px;\n padding-top: 16px;\n padding-bottom: 16px;\n text-align: right;\n .o-button {\n border: 1px solid lightgrey;\n padding: 0px 20px 0px 20px;\n border-radius: 4px;\n font-weight: 500;\n font-size: 14px;\n height: 30px;\n line-height: 16px;\n background: white;\n margin-right: 8px;\n &:hover:enabled {\n background-color: rgba(0, 0, 0, 0.08);\n }\n }\n .o-button:enabled {\n cursor: pointer;\n }\n .o-button:last-child {\n margin-right: 0px;\n }\n }\n input {\n box-sizing: border-box;\n width: 100%;\n border-radius: 4px;\n padding: 4px 23px 4px 10px;\n border: none;\n height: 24px;\n border: 1px solid lightgrey;\n }\n .o-link-url {\n position: relative;\n flex-grow: 1;\n button {\n position: absolute;\n right: 0px;\n top: 0px;\n border: none;\n height: 20px;\n width: 20px;\n background-color: #fff;\n margin: 2px 3px 1px 0px;\n padding: 0px 1px 0px 0px;\n }\n button:hover {\n cursor: pointer;\n }\n }\n }\n`;\n class LinkEditor extends owl.Component {\n constructor() {\n super(...arguments);\n this.menuItems = linkMenuRegistry.getAll();\n this.link = owl.useState(this.defaultState);\n this.menu = owl.useState({\n isOpen: false,\n });\n this.linkEditorRef = owl.useRef(\"linkEditor\");\n this.position = useAbsoluteBoundingRect(this.linkEditorRef);\n this.urlInput = owl.useRef(\"urlInput\");\n }\n setup() {\n owl.onMounted(() => { var _a; return (_a = this.urlInput.el) === null || _a === void 0 ? void 0 : _a.focus(); });\n }\n get defaultState() {\n const { col, row } = this.props.cellPosition;\n const sheetId = this.env.model.getters.getActiveSheetId();\n const cell = this.env.model.getters.getEvaluatedCell({ sheetId, col, row });\n if (cell.link) {\n return {\n url: cell.link.url,\n label: cell.formattedValue,\n isUrlEditable: cell.link.isUrlEditable,\n };\n }\n return {\n label: cell.formattedValue,\n url: \"\",\n isUrlEditable: true,\n };\n }\n get menuPosition() {\n return {\n x: this.position.x + MENU_OFFSET_X - PADDING - 2,\n y: this.position.y + MENU_OFFSET_Y,\n };\n }\n onSpecialLink(ev) {\n const { detail: markdownLink } = ev;\n const link = detectLink(markdownLink);\n if (!link) {\n return;\n }\n this.link.url = link.url;\n this.link.label = link.label;\n this.link.isUrlEditable = link.isUrlEditable;\n }\n getUrlRepresentation(link) {\n return urlRepresentation(link, this.env.model.getters);\n }\n openMenu() {\n this.menu.isOpen = true;\n }\n removeLink() {\n this.link.url = \"\";\n this.link.isUrlEditable = true;\n }\n save() {\n var _a, _b;\n const { col, row } = this.props.cellPosition;\n const label = this.link.label || this.link.url;\n this.env.model.dispatch(\"UPDATE_CELL\", {\n col: col,\n row: row,\n sheetId: this.env.model.getters.getActiveSheetId(),\n content: markdownLink(label, this.link.url),\n });\n (_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);\n }\n cancel() {\n var _a, _b;\n (_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);\n }\n onKeyDown(ev) {\n switch (ev.key) {\n case \"Enter\":\n if (this.link.url) {\n this.save();\n }\n ev.stopPropagation();\n break;\n case \"Escape\":\n this.cancel();\n ev.stopPropagation();\n break;\n }\n }\n }\n LinkEditor.template = \"o-spreadsheet-LinkEditor\";\n LinkEditor.components = { Menu };\n const LinkEditorPopoverBuilder = {\n onOpen: (position, getters) => {\n return {\n isOpen: true,\n props: { cellPosition: position },\n Component: LinkEditor,\n cellCorner: \"BottomLeft\",\n };\n },\n };\n LinkEditor.props = {\n cellPosition: Object,\n onClosed: { type: Function, optional: true },\n };\n\n const cellPopoverRegistry = new Registry();\n cellPopoverRegistry\n .add(\"ErrorToolTip\", ErrorToolTipPopoverBuilder)\n .add(\"LinkCell\", LinkCellPopoverBuilder)\n .add(\"LinkEditor\", LinkEditorPopoverBuilder)\n .add(\"FilterMenu\", FilterMenuPopoverBuilder);\n\n /**\n * Registry intended to support usual currencies. It is mainly used to create\n * currency formats that can be selected or modified when customizing formats.\n */\n const currenciesRegistry = new Registry();\n\n class ImageFigure extends owl.Component {\n constructor() {\n super(...arguments);\n this.menuState = owl.useState({ isOpen: false, position: null, menuItems: [] });\n this.imageContainerRef = owl.useRef(\"o-image\");\n this.menuButtonRef = owl.useRef(\"menuButton\");\n this.menuButtonRect = useAbsoluteBoundingRect(this.menuButtonRef);\n this.position = useAbsoluteBoundingRect(this.imageContainerRef);\n }\n getMenuItemRegistry() {\n const registry = new MenuItemRegistry();\n registry.add(\"copy\", {\n name: _lt(\"Copy\"),\n description: \"Ctrl+C\",\n sequence: 1,\n action: async () => {\n this.env.model.dispatch(\"SELECT_FIGURE\", { id: this.figureId });\n this.env.model.dispatch(\"COPY\");\n await this.env.clipboard.write(this.env.model.getters.getClipboardContent());\n },\n });\n registry.add(\"cut\", {\n name: _lt(\"Cut\"),\n description: \"Ctrl+X\",\n sequence: 2,\n action: async () => {\n this.env.model.dispatch(\"SELECT_FIGURE\", { id: this.figureId });\n this.env.model.dispatch(\"CUT\");\n await this.env.clipboard.write(this.env.model.getters.getClipboardContent());\n },\n });\n registry.add(\"reset_size\", {\n name: _lt(\"Reset size\"),\n sequence: 3,\n action: () => {\n const size = this.env.model.getters.getImageSize(this.figureId);\n const { height, width } = getMaxFigureSize(this.env.model.getters, size);\n this.env.model.dispatch(\"UPDATE_FIGURE\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n id: this.figureId,\n height,\n width,\n });\n },\n });\n registry.add(\"delete\", {\n name: _lt(\"Delete image\"),\n description: \"delete\",\n sequence: 5,\n action: () => {\n this.env.model.dispatch(\"DELETE_FIGURE\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n id: this.figureId,\n });\n },\n });\n return registry;\n }\n onContextMenu(ev) {\n const position = {\n x: this.position.x + ev.offsetX,\n y: this.position.y + ev.offsetY,\n };\n this.openContextMenu(position);\n }\n showMenu() {\n const { x, y, width } = this.menuButtonRect;\n const menuPosition = {\n x: x >= MENU_WIDTH ? x - MENU_WIDTH : x + width,\n y: y,\n };\n this.openContextMenu(menuPosition);\n }\n openContextMenu(position) {\n const registry = this.getMenuItemRegistry();\n this.menuState.isOpen = true;\n this.menuState.menuItems = registry.getAll().filter((x) => x.isVisible(this.env));\n this.menuState.position = position;\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n get figureId() {\n return this.props.figure.id;\n }\n get getImagePath() {\n return this.env.model.getters.getImagePath(this.figureId);\n }\n }\n ImageFigure.template = \"o-spreadsheet-ImageFigure\";\n ImageFigure.components = { Menu };\n ImageFigure.props = {\n figure: Object,\n onFigureDeleted: Function,\n };\n\n const figureRegistry = new Registry();\n figureRegistry.add(\"chart\", { Component: ChartFigure, SidePanelComponent: \"ChartPanel\" });\n figureRegistry.add(\"image\", {\n Component: ImageFigure,\n keepRatio: true,\n minFigSize: 20,\n borderWidth: 0,\n });\n\n const inverseCommandRegistry = new Registry()\n .add(\"ADD_COLUMNS_ROWS\", inverseAddColumnsRows)\n .add(\"REMOVE_COLUMNS_ROWS\", inverseRemoveColumnsRows)\n .add(\"ADD_MERGE\", inverseAddMerge)\n .add(\"REMOVE_MERGE\", inverseRemoveMerge)\n .add(\"CREATE_SHEET\", inverseCreateSheet)\n .add(\"DELETE_SHEET\", inverseDeleteSheet)\n .add(\"DUPLICATE_SHEET\", inverseDuplicateSheet)\n .add(\"CREATE_FIGURE\", inverseCreateFigure)\n .add(\"CREATE_CHART\", inverseCreateChart)\n .add(\"HIDE_COLUMNS_ROWS\", inverseHideColumnsRows)\n .add(\"UNHIDE_COLUMNS_ROWS\", inverseUnhideColumnsRows);\n for (const cmd of coreTypes.values()) {\n if (!inverseCommandRegistry.contains(cmd)) {\n inverseCommandRegistry.add(cmd, identity);\n }\n }\n function identity(cmd) {\n return [cmd];\n }\n function inverseAddColumnsRows(cmd) {\n const elements = [];\n let start = cmd.base;\n if (cmd.position === \"after\") {\n start++;\n }\n for (let i = 0; i < cmd.quantity; i++) {\n elements.push(i + start);\n }\n return [\n {\n type: \"REMOVE_COLUMNS_ROWS\",\n dimension: cmd.dimension,\n elements,\n sheetId: cmd.sheetId,\n },\n ];\n }\n function inverseAddMerge(cmd) {\n return [{ type: \"REMOVE_MERGE\", sheetId: cmd.sheetId, target: cmd.target }];\n }\n function inverseRemoveMerge(cmd) {\n return [{ type: \"ADD_MERGE\", sheetId: cmd.sheetId, target: cmd.target }];\n }\n function inverseCreateSheet(cmd) {\n return [{ type: \"DELETE_SHEET\", sheetId: cmd.sheetId }];\n }\n function inverseDuplicateSheet(cmd) {\n return [{ type: \"DELETE_SHEET\", sheetId: cmd.sheetIdTo }];\n }\n function inverseRemoveColumnsRows(cmd) {\n const commands = [];\n const elements = [...cmd.elements].sort((a, b) => a - b);\n for (let group of groupConsecutive(elements)) {\n const column = group[0] === 0 ? 0 : group[0] - 1;\n const position = group[0] === 0 ? \"before\" : \"after\";\n commands.push({\n type: \"ADD_COLUMNS_ROWS\",\n dimension: cmd.dimension,\n quantity: group.length,\n base: column,\n sheetId: cmd.sheetId,\n position,\n });\n }\n return commands;\n }\n function inverseDeleteSheet(cmd) {\n return [{ type: \"CREATE_SHEET\", sheetId: cmd.sheetId, position: 1 }];\n }\n function inverseCreateFigure(cmd) {\n return [{ type: \"DELETE_FIGURE\", id: cmd.figure.id, sheetId: cmd.sheetId }];\n }\n function inverseCreateChart(cmd) {\n return [{ type: \"DELETE_FIGURE\", id: cmd.id, sheetId: cmd.sheetId }];\n }\n function inverseHideColumnsRows(cmd) {\n return [\n {\n type: \"UNHIDE_COLUMNS_ROWS\",\n sheetId: cmd.sheetId,\n dimension: cmd.dimension,\n elements: cmd.elements,\n },\n ];\n }\n function inverseUnhideColumnsRows(cmd) {\n return [\n {\n type: \"HIDE_COLUMNS_ROWS\",\n sheetId: cmd.sheetId,\n dimension: cmd.dimension,\n elements: cmd.elements,\n },\n ];\n }\n\n function interactiveRenameSheet(env, sheetId, errorText) {\n const placeholder = env.model.getters.getSheetName(sheetId);\n const title = _lt(\"Rename Sheet\");\n const callback = (name) => {\n if (name === null || name === placeholder) {\n return;\n }\n if (name === \"\") {\n interactiveRenameSheet(env, sheetId, _lt(\"The sheet name cannot be empty.\"));\n }\n const result = env.model.dispatch(\"RENAME_SHEET\", { sheetId, name });\n if (!result.isSuccessful) {\n if (result.reasons.includes(11 /* CommandResult.DuplicatedSheetName */)) {\n interactiveRenameSheet(env, sheetId, _lt(\"A sheet with the name %s already exists. Please select another name.\", name));\n }\n if (result.reasons.includes(13 /* CommandResult.ForbiddenCharactersInSheetName */)) {\n interactiveRenameSheet(env, sheetId, _lt(\"Some used characters are not allowed in a sheet name (Forbidden characters are %s).\", FORBIDDEN_SHEET_CHARS.join(\" \")));\n }\n }\n };\n env.editText(title, callback, {\n placeholder: placeholder,\n error: errorText,\n });\n }\n\n const sheetMenuRegistry = new MenuItemRegistry();\n sheetMenuRegistry\n .add(\"delete\", {\n name: _lt(\"Delete\"),\n sequence: 10,\n isVisible: (env) => {\n return env.model.getters.getSheetIds().length > 1;\n },\n action: (env) => env.askConfirmation(_lt(\"Are you sure you want to delete this sheet ?\"), () => {\n env.model.dispatch(\"DELETE_SHEET\", { sheetId: env.model.getters.getActiveSheetId() });\n }),\n })\n .add(\"duplicate\", {\n name: _lt(\"Duplicate\"),\n sequence: 20,\n action: (env) => {\n const sheetIdFrom = env.model.getters.getActiveSheetId();\n const sheetIdTo = env.model.uuidGenerator.uuidv4();\n env.model.dispatch(\"DUPLICATE_SHEET\", {\n sheetId: sheetIdFrom,\n sheetIdTo,\n });\n env.model.dispatch(\"ACTIVATE_SHEET\", { sheetIdFrom, sheetIdTo });\n },\n })\n .add(\"rename\", {\n name: _lt(\"Rename\"),\n sequence: 30,\n action: (env) => interactiveRenameSheet(env, env.model.getters.getActiveSheetId()),\n })\n .add(\"move_right\", {\n name: _lt(\"Move right\"),\n sequence: 40,\n isVisible: (env) => {\n const sheetId = env.model.getters.getActiveSheetId();\n const sheetIds = env.model.getters.getVisibleSheetIds();\n return sheetIds.indexOf(sheetId) !== sheetIds.length - 1;\n },\n action: (env) => env.model.dispatch(\"MOVE_SHEET\", {\n sheetId: env.model.getters.getActiveSheetId(),\n direction: \"right\",\n }),\n })\n .add(\"move_left\", {\n name: _lt(\"Move left\"),\n sequence: 50,\n isVisible: (env) => {\n const sheetId = env.model.getters.getActiveSheetId();\n return env.model.getters.getVisibleSheetIds()[0] !== sheetId;\n },\n action: (env) => env.model.dispatch(\"MOVE_SHEET\", {\n sheetId: env.model.getters.getActiveSheetId(),\n direction: \"left\",\n }),\n })\n .add(\"hide_sheet\", {\n name: _lt(\"Hide sheet\"),\n sequence: 60,\n isVisible: (env) => env.model.getters.getVisibleSheetIds().length !== 1,\n action: (env) => env.model.dispatch(\"HIDE_SHEET\", { sheetId: env.model.getters.getActiveSheetId() }),\n });\n\n function interactiveFreezeColumnsRows(env, dimension, base) {\n const sheetId = env.model.getters.getActiveSheetId();\n const cmd = dimension === \"COL\" ? \"FREEZE_COLUMNS\" : \"FREEZE_ROWS\";\n const result = env.model.dispatch(cmd, { sheetId, quantity: base });\n if (result.isCancelledBecause(65 /* CommandResult.MergeOverlap */)) {\n env.raiseError(MergeErrorMessage);\n }\n }\n\n const topbarMenuRegistry = new MenuItemRegistry();\n topbarMenuRegistry\n .add(\"file\", { name: _lt(\"File\"), sequence: 10 })\n .add(\"edit\", { name: _lt(\"Edit\"), sequence: 20 })\n .add(\"view\", { name: _lt(\"View\"), sequence: 30 })\n .add(\"insert\", { name: _lt(\"Insert\"), sequence: 40 })\n .add(\"format\", { name: _lt(\"Format\"), sequence: 50 })\n .add(\"data\", { name: _lt(\"Data\"), sequence: 60 })\n .addChild(\"save\", [\"file\"], {\n name: _lt(\"Save\"),\n description: \"Ctrl+S\",\n sequence: 10,\n action: () => console.log(\"Not implemented\"),\n })\n .addChild(\"undo\", [\"edit\"], {\n name: _lt(\"Undo\"),\n description: \"Ctrl+Z\",\n sequence: 10,\n action: UNDO_ACTION,\n })\n .addChild(\"redo\", [\"edit\"], {\n name: _lt(\"Redo\"),\n description: \"Ctrl+Y\",\n sequence: 20,\n action: REDO_ACTION,\n separator: true,\n })\n .addChild(\"copy\", [\"edit\"], {\n name: _lt(\"Copy\"),\n description: \"Ctrl+C\",\n sequence: 30,\n isReadonlyAllowed: true,\n action: COPY_ACTION,\n })\n .addChild(\"cut\", [\"edit\"], {\n name: _lt(\"Cut\"),\n description: \"Ctrl+X\",\n sequence: 40,\n action: CUT_ACTION,\n })\n .addChild(\"paste\", [\"edit\"], {\n name: _lt(\"Paste\"),\n description: \"Ctrl+V\",\n sequence: 50,\n action: PASTE_ACTION,\n })\n .addChild(\"paste_special\", [\"edit\"], {\n name: _lt(\"Paste special\"),\n sequence: 60,\n separator: true,\n isVisible: IS_NOT_CUT_OPERATION,\n })\n .addChild(\"paste_special_value\", [\"edit\", \"paste_special\"], {\n name: _lt(\"Paste value only\"),\n sequence: 10,\n action: PASTE_VALUE_ACTION,\n })\n .addChild(\"paste_special_format\", [\"edit\", \"paste_special\"], {\n name: _lt(\"Paste format only\"),\n sequence: 20,\n action: PASTE_FORMAT_ACTION,\n })\n .addChild(\"sort_range\", [\"data\"], {\n name: _lt(\"Sort range\"),\n sequence: 20,\n isVisible: IS_ONLY_ONE_RANGE,\n separator: true,\n })\n .addChild(\"sort_ascending\", [\"data\", \"sort_range\"], {\n name: _lt(\"Ascending (A \u27f6 Z)\"),\n sequence: 10,\n action: SORT_CELLS_ASCENDING,\n })\n .addChild(\"sort_descending\", [\"data\", \"sort_range\"], {\n name: _lt(\"Descending (Z \u27f6 A)\"),\n sequence: 20,\n action: SORT_CELLS_DESCENDING,\n })\n .addChild(\"find_and_replace\", [\"edit\"], {\n name: _lt(\"Find and replace\"),\n description: \"Ctrl+H\",\n sequence: 65,\n isReadonlyAllowed: true,\n action: OPEN_FAR_SIDEPANEL_ACTION,\n separator: true,\n })\n .addChild(\"edit_delete_cell_values\", [\"edit\"], {\n name: _lt(\"Delete values\"),\n sequence: 70,\n action: DELETE_CONTENT_ACTION,\n })\n .addChild(\"edit_delete_row\", [\"edit\"], {\n name: REMOVE_ROWS_NAME,\n sequence: 80,\n action: REMOVE_ROWS_ACTION,\n })\n .addChild(\"edit_delete_column\", [\"edit\"], {\n name: REMOVE_COLUMNS_NAME,\n sequence: 90,\n action: REMOVE_COLUMNS_ACTION,\n })\n .addChild(\"edit_delete_cell_shift_up\", [\"edit\"], {\n name: _lt(\"Delete cell and shift up\"),\n sequence: 93,\n action: DELETE_CELL_SHIFT_UP,\n })\n .addChild(\"edit_delete_cell_shift_left\", [\"edit\"], {\n name: _lt(\"Delete cell and shift left\"),\n sequence: 97,\n action: DELETE_CELL_SHIFT_LEFT,\n })\n .addChild(\"edit_unhide_columns\", [\"edit\"], {\n name: _lt(\"Unhide all columns\"),\n sequence: 100,\n action: UNHIDE_ALL_COLUMNS_ACTION,\n isVisible: (env) => env.model.getters.getHiddenColsGroups(env.model.getters.getActiveSheetId()).length > 0,\n })\n .addChild(\"edit_unhide_rows\", [\"edit\"], {\n name: _lt(\"Unhide all rows\"),\n sequence: 100,\n action: UNHIDE_ALL_ROWS_ACTION,\n isVisible: (env) => env.model.getters.getHiddenRowsGroups(env.model.getters.getActiveSheetId()).length > 0,\n })\n .addChild(\"insert_row_before\", [\"insert\"], {\n name: MENU_INSERT_ROWS_BEFORE_NAME,\n sequence: 10,\n action: INSERT_ROWS_BEFORE_ACTION,\n isVisible: (env) => env.model.getters.getActiveCols().size === 0,\n })\n .addChild(\"insert_row_after\", [\"insert\"], {\n name: MENU_INSERT_ROWS_AFTER_NAME,\n sequence: 20,\n action: INSERT_ROWS_AFTER_ACTION,\n isVisible: (env) => env.model.getters.getActiveCols().size === 0,\n separator: true,\n })\n .addChild(\"insert_column_before\", [\"insert\"], {\n name: MENU_INSERT_COLUMNS_BEFORE_NAME,\n sequence: 30,\n action: INSERT_COLUMNS_BEFORE_ACTION,\n isVisible: (env) => env.model.getters.getActiveRows().size === 0,\n })\n .addChild(\"insert_column_after\", [\"insert\"], {\n name: MENU_INSERT_COLUMNS_AFTER_NAME,\n sequence: 40,\n action: INSERT_COLUMNS_AFTER_ACTION,\n isVisible: (env) => env.model.getters.getActiveRows().size === 0,\n separator: true,\n })\n .addChild(\"insert_insert_cell_shift_down\", [\"insert\"], {\n name: _lt(\"Insert cells and shift down\"),\n sequence: 43,\n action: INSERT_CELL_SHIFT_DOWN,\n })\n .addChild(\"insert_insert_cell_shift_right\", [\"insert\"], {\n name: _lt(\"Insert cells and shift right\"),\n sequence: 47,\n action: INSERT_CELL_SHIFT_RIGHT,\n separator: true,\n })\n .addChild(\"insert_chart\", [\"insert\"], {\n name: _lt(\"Chart\"),\n sequence: 50,\n action: CREATE_CHART,\n })\n .addChild(\"insert_image\", [\"insert\"], {\n name: _lt(\"Image\"),\n sequence: 55,\n action: CREATE_IMAGE,\n isVisible: (env) => env.imageProvider !== undefined,\n })\n .addChild(\"insert_link\", [\"insert\"], {\n name: _lt(\"Link\"),\n separator: true,\n sequence: 60,\n action: INSERT_LINK,\n })\n .addChild(\"insert_sheet\", [\"insert\"], {\n name: _lt(\"New sheet\"),\n sequence: 70,\n action: CREATE_SHEET_ACTION,\n separator: true,\n })\n .addChild(\"unfreeze_panes\", [\"view\"], {\n name: _lt(\"Unfreeze\"),\n sequence: 4,\n isVisible: (env) => {\n const { xSplit, ySplit } = env.model.getters.getPaneDivisions(env.model.getters.getActiveSheetId());\n return xSplit + ySplit > 0;\n },\n action: (env) => env.model.dispatch(\"UNFREEZE_COLUMNS_ROWS\", {\n sheetId: env.model.getters.getActiveSheetId(),\n }),\n })\n .addChild(\"freeze_panes\", [\"view\"], {\n name: _lt(\"Freeze\"),\n sequence: 5,\n separator: true,\n })\n .addChild(\"unfreeze_rows\", [\"view\", \"freeze_panes\"], {\n name: _lt(\"No rows\"),\n action: (env) => env.model.dispatch(\"UNFREEZE_ROWS\", {\n sheetId: env.model.getters.getActiveSheetId(),\n }),\n isReadonlyAllowed: true,\n sequence: 5,\n isVisible: (env) => !!env.model.getters.getPaneDivisions(env.model.getters.getActiveSheetId()).ySplit,\n })\n .addChild(\"freeze_first_row\", [\"view\", \"freeze_panes\"], {\n name: _lt(\"1 row\"),\n action: (env) => interactiveFreezeColumnsRows(env, \"ROW\", 1),\n isReadonlyAllowed: true,\n sequence: 10,\n })\n .addChild(\"freeze_second_row\", [\"view\", \"freeze_panes\"], {\n name: _lt(\"2 rows\"),\n action: (env) => interactiveFreezeColumnsRows(env, \"ROW\", 2),\n isReadonlyAllowed: true,\n sequence: 15,\n })\n .addChild(\"freeze_current_row\", [\"view\", \"freeze_panes\"], {\n name: _lt(\"Up to current row\"),\n action: (env) => {\n const { bottom } = env.model.getters.getSelectedZone();\n interactiveFreezeColumnsRows(env, \"ROW\", bottom + 1);\n },\n isReadonlyAllowed: true,\n sequence: 20,\n separator: true,\n })\n .addChild(\"unfreeze_columns\", [\"view\", \"freeze_panes\"], {\n name: _lt(\"No columns\"),\n action: (env) => env.model.dispatch(\"UNFREEZE_COLUMNS\", {\n sheetId: env.model.getters.getActiveSheetId(),\n }),\n isReadonlyAllowed: true,\n sequence: 25,\n isVisible: (env) => !!env.model.getters.getPaneDivisions(env.model.getters.getActiveSheetId()).xSplit,\n })\n .addChild(\"freeze_first_col\", [\"view\", \"freeze_panes\"], {\n name: _lt(\"1 column\"),\n action: (env) => interactiveFreezeColumnsRows(env, \"COL\", 1),\n isReadonlyAllowed: true,\n sequence: 30,\n })\n .addChild(\"freeze_second_col\", [\"view\", \"freeze_panes\"], {\n name: _lt(\"2 columns\"),\n action: (env) => interactiveFreezeColumnsRows(env, \"COL\", 2),\n isReadonlyAllowed: true,\n sequence: 35,\n })\n .addChild(\"freeze_current_col\", [\"view\", \"freeze_panes\"], {\n name: _lt(\"Up to current column\"),\n action: (env) => {\n const { right } = env.model.getters.getSelectedZone();\n interactiveFreezeColumnsRows(env, \"COL\", right + 1);\n },\n isReadonlyAllowed: true,\n sequence: 40,\n })\n .addChild(\"view_gridlines\", [\"view\"], {\n name: (env) => env.model.getters.getGridLinesVisibility(env.model.getters.getActiveSheetId())\n ? _lt(\"Hide gridlines\")\n : _lt(\"Show gridlines\"),\n action: SET_GRID_LINES_VISIBILITY_ACTION,\n sequence: 10,\n })\n .addChild(\"view_formulas\", [\"view\"], {\n name: (env) => env.model.getters.shouldShowFormulas() ? _lt(\"Hide formulas\") : _lt(\"Show formulas\"),\n action: SET_FORMULA_VISIBILITY_ACTION,\n isReadonlyAllowed: true,\n sequence: 15,\n })\n .addChild(\"format_number\", [\"format\"], {\n name: _lt(\"Numbers\"),\n sequence: 10,\n separator: true,\n })\n .addChild(\"format_number_automatic\", [\"format\", \"format_number\"], {\n name: NumberFormatTerms.Automatic,\n sequence: 10,\n separator: true,\n action: FORMAT_AUTOMATIC_ACTION,\n })\n .addChild(\"format_number_number\", [\"format\", \"format_number\"], {\n name: NumberFormatTerms.Number,\n description: \"1,000.12\",\n sequence: 20,\n action: FORMAT_NUMBER_ACTION,\n })\n .addChild(\"format_number_percent\", [\"format\", \"format_number\"], {\n name: NumberFormatTerms.Percent,\n description: \"10.12%\",\n sequence: 30,\n separator: true,\n action: FORMAT_PERCENT_ACTION,\n })\n .addChild(\"format_number_currency\", [\"format\", \"format_number\"], {\n name: NumberFormatTerms.Currency,\n description: \"$1,000.12\",\n sequence: 37,\n action: FORMAT_CURRENCY_ACTION,\n })\n .addChild(\"format_number_currency_rounded\", [\"format\", \"format_number\"], {\n name: NumberFormatTerms.CurrencyRounded,\n description: \"$1,000\",\n sequence: 38,\n action: FORMAT_CURRENCY_ROUNDED_ACTION,\n })\n .addChild(\"format_custom_currency\", [\"format\", \"format_number\"], {\n name: NumberFormatTerms.CustomCurrency,\n sequence: 39,\n separator: true,\n isVisible: (env) => env.loadCurrencies !== undefined,\n action: OPEN_CUSTOM_CURRENCY_SIDEPANEL_ACTION,\n })\n .addChild(\"format_number_date\", [\"format\", \"format_number\"], {\n name: NumberFormatTerms.Date,\n description: \"9/26/2008\",\n sequence: 40,\n action: FORMAT_DATE_ACTION,\n })\n .addChild(\"format_number_time\", [\"format\", \"format_number\"], {\n name: NumberFormatTerms.Time,\n description: \"10:43:00 PM\",\n sequence: 50,\n action: FORMAT_TIME_ACTION,\n })\n .addChild(\"format_number_date_time\", [\"format\", \"format_number\"], {\n name: NumberFormatTerms.DateTime,\n description: \"9/26/2008 22:43:00\",\n sequence: 60,\n action: FORMAT_DATE_TIME_ACTION,\n })\n .addChild(\"format_number_duration\", [\"format\", \"format_number\"], {\n name: NumberFormatTerms.Duration,\n description: \"27:51:38\",\n sequence: 70,\n separator: true,\n action: FORMAT_DURATION_ACTION,\n })\n .addChild(\"format_bold\", [\"format\"], {\n name: _lt(\"Bold\"),\n sequence: 20,\n description: \"Ctrl+B\",\n action: FORMAT_BOLD_ACTION,\n })\n .addChild(\"format_italic\", [\"format\"], {\n name: _lt(\"Italic\"),\n sequence: 30,\n description: \"Ctrl+I\",\n action: FORMAT_ITALIC_ACTION,\n })\n .addChild(\"format_underline\", [\"format\"], {\n name: _lt(\"Underline\"),\n description: \"Ctrl+U\",\n sequence: 40,\n action: FORMAT_UNDERLINE_ACTION,\n })\n .addChild(\"format_strikethrough\", [\"format\"], {\n name: _lt(\"Strikethrough\"),\n sequence: 50,\n action: FORMAT_STRIKETHROUGH_ACTION,\n separator: true,\n })\n .addChild(\"format_font_size\", [\"format\"], {\n name: _lt(\"Font size\"),\n sequence: 60,\n separator: true,\n })\n .addChild(\"format_alignment\", [\"format\"], {\n name: _lt(\"Alignment\"),\n sequence: 70,\n })\n .addChild(\"format_alignment_left\", [\"format\", \"format_alignment\"], {\n name: \"Left\",\n sequence: 10,\n action: (env) => setStyle(env, { align: \"left\" }),\n })\n .addChild(\"format_alignment_center\", [\"format\", \"format_alignment\"], {\n name: \"Center\",\n sequence: 20,\n action: (env) => setStyle(env, { align: \"center\" }),\n })\n .addChild(\"format_alignment_right\", [\"format\", \"format_alignment\"], {\n name: \"Right\",\n sequence: 30,\n action: (env) => setStyle(env, { align: \"right\" }),\n separator: true,\n })\n .addChild(\"format_alignment_top\", [\"format\", \"format_alignment\"], {\n name: \"Top\",\n sequence: 40,\n action: (env) => setStyle(env, { verticalAlign: \"top\" }),\n })\n .addChild(\"format_alignment_middle\", [\"format\", \"format_alignment\"], {\n name: \"Middle\",\n sequence: 50,\n action: (env) => setStyle(env, { verticalAlign: \"middle\" }),\n })\n .addChild(\"format_alignment_bottom\", [\"format\", \"format_alignment\"], {\n name: \"Bottom\",\n sequence: 60,\n action: (env) => setStyle(env, { verticalAlign: \"bottom\" }),\n separator: true,\n })\n .addChild(\"format_wrapping\", [\"format\"], {\n name: _lt(\"Wrapping\"),\n sequence: 80,\n separator: true,\n })\n .addChild(\"format_wrapping_overflow\", [\"format\", \"format_wrapping\"], {\n name: _lt(\"Overflow\"),\n sequence: 10,\n action: (env) => setStyle(env, { wrapping: \"overflow\" }),\n })\n .addChild(\"format_wrapping_wrap\", [\"format\", \"format_wrapping\"], {\n name: _lt(\"Wrap\"),\n sequence: 20,\n action: (env) => setStyle(env, { wrapping: \"wrap\" }),\n })\n .addChild(\"format_wrapping_clip\", [\"format\", \"format_wrapping\"], {\n name: _lt(\"Clip\"),\n sequence: 30,\n action: (env) => setStyle(env, { wrapping: \"clip\" }),\n })\n .addChild(\"format_cf\", [\"format\"], {\n name: _lt(\"Conditional formatting\"),\n sequence: 90,\n action: OPEN_CF_SIDEPANEL_ACTION,\n separator: true,\n })\n .addChild(\"format_clearFormat\", [\"format\"], {\n name: _lt(\"Clear formatting\"),\n sequence: 100,\n action: FORMAT_CLEARFORMAT_ACTION,\n separator: true,\n })\n .addChild(\"add_data_filter\", [\"data\"], {\n name: _lt(\"Create filter\"),\n sequence: 10,\n action: FILTERS_CREATE_FILTER_TABLE,\n isVisible: (env) => !SELECTION_CONTAINS_FILTER(env),\n isEnabled: (env) => SELECTION_IS_CONTINUOUS(env),\n })\n .addChild(\"remove_data_filter\", [\"data\"], {\n name: _lt(\"Remove filter\"),\n sequence: 10,\n action: FILTERS_REMOVE_FILTER_TABLE,\n isVisible: SELECTION_CONTAINS_FILTER,\n });\n // Font-sizes\n for (let fs of fontSizes) {\n topbarMenuRegistry.addChild(`format_font_size_${fs.pt}`, [\"format\", \"format_font_size\"], {\n name: fs.pt.toString(),\n sequence: fs.pt,\n action: (env) => setStyle(env, { fontSize: fs.pt }),\n });\n }\n\n class OTRegistry extends Registry {\n /**\n * Add a transformation function to the registry. When the executed command\n * happened, all the commands in toTransforms should be transformed using the\n * transformation function given\n */\n addTransformation(executed, toTransforms, fn) {\n for (let toTransform of toTransforms) {\n if (!this.content[toTransform]) {\n this.content[toTransform] = new Map();\n }\n this.content[toTransform].set(executed, fn);\n }\n return this;\n }\n /**\n * Get the transformation function to transform the command toTransform, after\n * that the executed command happened.\n */\n getTransformation(toTransform, executed) {\n return this.content[toTransform] && this.content[toTransform].get(executed);\n }\n }\n const otRegistry = new OTRegistry();\n\n const uuidGenerator = new UuidGenerator();\n css /* scss */ `\n .o-selection {\n .o-selection-input {\n display: flex;\n flex-direction: row;\n\n input {\n padding: 4px 6px;\n border-radius: 4px;\n box-sizing: border-box;\n flex-grow: 2;\n }\n input:focus {\n outline: none;\n }\n input.o-required,\n input.o-focused {\n border-width: 2px;\n padding: 3px 5px;\n }\n input.o-focused {\n border-color: ${SELECTION_BORDER_COLOR};\n }\n input.o-invalid {\n border-color: red;\n }\n button.o-btn {\n background: transparent;\n border: none;\n color: #333;\n cursor: pointer;\n }\n button.o-btn-action {\n margin: 8px 1px;\n border-radius: 4px;\n background: transparent;\n border: 1px solid #dadce0;\n color: #188038;\n font-weight: bold;\n font-size: 14px;\n height: 25px;\n }\n }\n }\n`;\n /**\n * This component can be used when the user needs to input some\n * ranges. He can either input the ranges with the regular DOM ``\n * displayed or by selecting zones on the grid.\n *\n * onSelectionChanged is called every time the input value\n * changes.\n */\n class SelectionInput extends owl.Component {\n constructor() {\n super(...arguments);\n this.id = uuidGenerator.uuidv4();\n this.previousRanges = this.props.ranges || [];\n this.originSheet = this.env.model.getters.getActiveSheetId();\n this.state = owl.useState({\n isMissing: false,\n mode: \"select-range\",\n });\n }\n get ranges() {\n const existingSelectionRange = this.env.model.getters.getSelectionInput(this.id);\n const ranges = existingSelectionRange.length\n ? existingSelectionRange\n : this.props.ranges\n ? this.props.ranges.map((xc, i) => ({\n xc,\n id: i.toString(),\n isFocused: false,\n }))\n : [];\n return ranges.map((range) => ({\n ...range,\n isValidRange: range.xc === \"\" || this.env.model.getters.isRangeValid(range.xc),\n }));\n }\n get hasFocus() {\n return this.ranges.filter((i) => i.isFocused).length > 0;\n }\n get canAddRange() {\n return !this.props.hasSingleRange;\n }\n get isInvalid() {\n return this.props.isInvalid || this.state.isMissing;\n }\n setup() {\n owl.onMounted(() => this.enableNewSelectionInput());\n owl.onWillUnmount(async () => this.disableNewSelectionInput());\n owl.onPatched(() => this.checkChange());\n }\n enableNewSelectionInput() {\n this.env.model.dispatch(\"ENABLE_NEW_SELECTION_INPUT\", {\n id: this.id,\n initialRanges: this.props.ranges,\n hasSingleRange: this.props.hasSingleRange,\n });\n }\n disableNewSelectionInput() {\n this.env.model.dispatch(\"DISABLE_SELECTION_INPUT\", { id: this.id });\n }\n checkChange() {\n const value = this.env.model.getters.getSelectionInputValue(this.id);\n if (this.previousRanges.join() !== value.join()) {\n this.triggerChange();\n }\n }\n getColor(range) {\n const color = range.color || \"#000\";\n return \"color: \" + color + \";\";\n }\n triggerChange() {\n var _a, _b;\n const ranges = this.env.model.getters.getSelectionInputValue(this.id);\n (_b = (_a = this.props).onSelectionChanged) === null || _b === void 0 ? void 0 : _b.call(_a, ranges);\n this.previousRanges = ranges;\n }\n onKeydown(ev) {\n if (ev.key === \"F2\") {\n ev.preventDefault();\n ev.stopPropagation();\n this.state.mode = this.state.mode === \"select-range\" ? \"text-edit\" : \"select-range\";\n }\n else if (ev.key.startsWith(\"Arrow\")) {\n ev.stopPropagation();\n if (this.state.mode === \"select-range\") {\n ev.preventDefault();\n updateSelectionWithArrowKeys(ev, this.env.model.selection);\n }\n }\n }\n focus(rangeId) {\n this.state.isMissing = false;\n this.state.mode = \"select-range\";\n this.env.model.dispatch(\"FOCUS_RANGE\", {\n id: this.id,\n rangeId,\n });\n }\n addEmptyInput() {\n this.env.model.dispatch(\"ADD_EMPTY_RANGE\", { id: this.id });\n }\n removeInput(rangeId) {\n var _a, _b;\n this.env.model.dispatch(\"REMOVE_RANGE\", { id: this.id, rangeId });\n this.triggerChange();\n (_b = (_a = this.props).onSelectionConfirmed) === null || _b === void 0 ? void 0 : _b.call(_a);\n }\n onInputChanged(rangeId, ev) {\n const target = ev.target;\n this.env.model.dispatch(\"CHANGE_RANGE\", {\n id: this.id,\n rangeId,\n value: target.value,\n });\n target.blur();\n this.triggerChange();\n }\n disable() {\n var _a, _b;\n this.env.model.dispatch(\"UNFOCUS_SELECTION_INPUT\");\n const ranges = this.env.model.getters.getSelectionInputValue(this.id);\n if (this.props.required && ranges.length === 0) {\n this.state.isMissing = true;\n }\n const activeSheetId = this.env.model.getters.getActiveSheetId();\n if (this.originSheet !== activeSheetId) {\n this.env.model.dispatch(\"ACTIVATE_SHEET\", {\n sheetIdFrom: activeSheetId,\n sheetIdTo: this.originSheet,\n });\n }\n (_b = (_a = this.props).onSelectionConfirmed) === null || _b === void 0 ? void 0 : _b.call(_a);\n }\n }\n SelectionInput.template = \"o-spreadsheet-SelectionInput\";\n SelectionInput.props = {\n ranges: Array,\n hasSingleRange: { type: Boolean, optional: true },\n required: { type: Boolean, optional: true },\n isInvalid: { type: Boolean, optional: true },\n class: { type: String, optional: true },\n onSelectionChanged: { type: Function, optional: true },\n onSelectionConfirmed: { type: Function, optional: true },\n };\n\n class LineBarPieConfigPanel extends owl.Component {\n constructor() {\n super(...arguments);\n this.state = owl.useState({\n datasetDispatchResult: undefined,\n labelsDispatchResult: undefined,\n });\n this.dataSeriesRanges = [];\n }\n setup() {\n this.dataSeriesRanges = this.props.definition.dataSets;\n this.labelRange = this.props.definition.labelRange;\n }\n get errorMessages() {\n var _a, _b;\n const cancelledReasons = [\n ...(((_a = this.state.datasetDispatchResult) === null || _a === void 0 ? void 0 : _a.reasons) || []),\n ...(((_b = this.state.labelsDispatchResult) === null || _b === void 0 ? void 0 : _b.reasons) || []),\n ];\n return cancelledReasons.map((error) => ChartTerms.Errors[error] || ChartTerms.Errors.Unexpected);\n }\n get isDatasetInvalid() {\n var _a;\n return !!((_a = this.state.datasetDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(32 /* CommandResult.InvalidDataSet */));\n }\n get isLabelInvalid() {\n var _a;\n return !!((_a = this.state.labelsDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(33 /* CommandResult.InvalidLabelRange */));\n }\n onUpdateDataSetsHaveTitle(ev) {\n this.props.updateChart({\n dataSetsHaveTitle: ev.target.checked,\n });\n }\n /**\n * Change the local dataSeriesRanges. The model should be updated when the\n * button \"confirm\" is clicked\n */\n onDataSeriesRangesChanged(ranges) {\n this.dataSeriesRanges = ranges;\n }\n onDataSeriesConfirmed() {\n this.state.datasetDispatchResult = this.props.updateChart({\n dataSets: this.dataSeriesRanges,\n });\n }\n /**\n * Change the local labelRange. The model should be updated when the\n * button \"confirm\" is clicked\n */\n onLabelRangeChanged(ranges) {\n this.labelRange = ranges[0];\n }\n onLabelRangeConfirmed() {\n this.state.labelsDispatchResult = this.props.updateChart({\n labelRange: this.labelRange,\n });\n }\n onUpdateAggregated(ev) {\n this.props.updateChart({\n aggregated: ev.target.checked,\n });\n }\n }\n LineBarPieConfigPanel.template = \"o-spreadsheet-LineBarPieConfigPanel\";\n LineBarPieConfigPanel.components = { SelectionInput };\n LineBarPieConfigPanel.props = {\n figureId: String,\n definition: Object,\n updateChart: Function,\n };\n\n class BarConfigPanel extends LineBarPieConfigPanel {\n onUpdateStacked(ev) {\n this.props.updateChart({\n stacked: ev.target.checked,\n });\n }\n onUpdateAggregated(ev) {\n this.props.updateChart({\n aggregated: ev.target.checked,\n });\n }\n }\n BarConfigPanel.template = \"o-spreadsheet-BarConfigPanel\";\n\n const PICKER_PADDING = 6;\n const LINE_VERTICAL_PADDING = 1;\n const LINE_HORIZONTAL_PADDING = 6;\n const ITEM_HORIZONTAL_MARGIN = 1;\n const ITEM_EDGE_LENGTH = 18;\n const ITEM_BORDER_WIDTH = 1;\n const ITEMS_PER_LINE = 10;\n const PICKER_WIDTH = ITEMS_PER_LINE * (ITEM_EDGE_LENGTH + ITEM_HORIZONTAL_MARGIN * 2 + 2 * ITEM_BORDER_WIDTH) +\n 2 * LINE_HORIZONTAL_PADDING;\n const GRADIENT_WIDTH = PICKER_WIDTH - 2 * LINE_HORIZONTAL_PADDING - 2 * ITEM_BORDER_WIDTH;\n const GRADIENT_HEIGHT = PICKER_WIDTH - 50;\n css /* scss */ `\n .o-color-picker {\n position: absolute;\n top: calc(100% + 5px);\n z-index: ${ComponentsImportance.ColorPicker};\n padding: ${PICKER_PADDING}px 0px;\n box-shadow: 1px 2px 5px 2px rgba(51, 51, 51, 0.15);\n background-color: white;\n line-height: 1.2;\n overflow-y: auto;\n overflow-x: hidden;\n width: ${GRADIENT_WIDTH + 2 * PICKER_PADDING}px;\n\n .o-color-picker-section-name {\n margin: 0px ${ITEM_HORIZONTAL_MARGIN}px;\n padding: 4px ${LINE_HORIZONTAL_PADDING}px;\n }\n .colors-grid {\n display: grid;\n padding: ${LINE_VERTICAL_PADDING}px ${LINE_HORIZONTAL_PADDING}px;\n grid-template-columns: repeat(${ITEMS_PER_LINE}, 1fr);\n grid-gap: ${ITEM_HORIZONTAL_MARGIN * 2}px;\n }\n .o-color-picker-line-item {\n width: ${ITEM_EDGE_LENGTH}px;\n height: ${ITEM_EDGE_LENGTH}px;\n margin: 0px;\n border-radius: 50px;\n border: ${ITEM_BORDER_WIDTH}px solid #666666;\n padding: 0px;\n font-size: 16px;\n background: white;\n &:hover {\n background-color: rgba(0, 0, 0, 0.08);\n outline: 1px solid gray;\n cursor: pointer;\n }\n }\n .o-buttons {\n padding: 6px;\n display: flex;\n .o-cancel {\n margin: 0px ${ITEM_HORIZONTAL_MARGIN}px;\n border: ${ITEM_BORDER_WIDTH}px solid #c0c0c0;\n width: 100%;\n padding: 5px;\n font-size: 14px;\n background: white;\n border-radius: 4px;\n &:hover:enabled {\n background-color: rgba(0, 0, 0, 0.08);\n }\n }\n }\n .o-add-button {\n border: ${ITEM_BORDER_WIDTH}px solid #c0c0c0;\n padding: 4px;\n background: white;\n border-radius: 4px;\n &:hover:enabled {\n background-color: rgba(0, 0, 0, 0.08);\n }\n }\n .o-separator {\n border-bottom: ${MENU_SEPARATOR_BORDER_WIDTH}px solid #e0e2e4;\n margin-top: ${MENU_SEPARATOR_PADDING}px;\n margin-bottom: ${MENU_SEPARATOR_PADDING}px;\n }\n input {\n box-sizing: border-box;\n width: 100%;\n border-radius: 4px;\n padding: 4px 23px 4px 10px;\n height: 24px;\n border: 1px solid #c0c0c0;\n margin: 0 2px 0 0;\n }\n input.o-wrong-color {\n border-color: red;\n }\n .o-custom-selector {\n padding: ${LINE_HORIZONTAL_PADDING}px;\n position: relative;\n .o-gradient {\n background: linear-gradient(to bottom, hsl(0 100% 0%), transparent, hsl(0 0% 100%)),\n linear-gradient(\n to right,\n hsl(0 100% 50%) 0%,\n hsl(0.2turn 100% 50%) 20%,\n hsl(0.3turn 100% 50%) 30%,\n hsl(0.4turn 100% 50%) 40%,\n hsl(0.5turn 100% 50%) 50%,\n hsl(0.6turn 100% 50%) 60%,\n hsl(0.7turn 100% 50%) 70%,\n hsl(0.8turn 100% 50%) 80%,\n hsl(0.9turn 100% 50%) 90%,\n hsl(1turn 100% 50%) 100%\n );\n border: ${ITEM_BORDER_WIDTH}px solid #c0c0c0;\n width: ${GRADIENT_WIDTH}px;\n height: ${GRADIENT_HEIGHT}px;\n &:hover {\n cursor: crosshair;\n }\n }\n .o-custom-input-preview {\n padding: 2px ${LINE_VERTICAL_PADDING}px;\n display: flex;\n }\n .o-custom-input-buttons {\n padding: 2px ${LINE_VERTICAL_PADDING}px;\n text-align: right;\n }\n .o-color-preview {\n border: 1px solid #c0c0c0;\n border-radius: 4px;\n width: 100%;\n }\n }\n &.right {\n left: 0;\n }\n &.left {\n right: 0;\n }\n &.center {\n left: calc(50% - ${PICKER_WIDTH / 2}px);\n }\n }\n .o-magnifier-glass {\n position: absolute;\n border: ${ITEM_BORDER_WIDTH}px solid #c0c0c0;\n border-radius: 50%;\n width: 30px;\n height: 30px;\n }\n`;\n function computeCustomColor(ev) {\n return rgbaToHex(hslaToRGBA({\n h: (360 * ev.offsetX) / GRADIENT_WIDTH,\n s: 100,\n l: (100 * ev.offsetY) / GRADIENT_HEIGHT,\n a: 1,\n }));\n }\n class ColorPicker extends owl.Component {\n constructor() {\n super(...arguments);\n this.COLORS = COLOR_PICKER_DEFAULTS;\n this.state = owl.useState({\n showGradient: false,\n currentColor: isColorValid(this.props.currentColor) ? this.props.currentColor : \"\",\n isCurrentColorInvalid: false,\n style: {\n display: \"none\",\n background: \"#ffffff\",\n left: \"0\",\n top: \"0\",\n },\n });\n }\n get colorPickerStyle() {\n if (this.props.maxHeight === undefined)\n return \"\";\n if (this.props.maxHeight <= 0) {\n return cssPropertiesToCss({ display: \"none\" });\n }\n return cssPropertiesToCss({\n \"max-height\": `${this.props.maxHeight}px`,\n });\n }\n onColorClick(color) {\n if (color) {\n this.props.onColorPicked(toHex(color));\n }\n }\n getCheckMarkColor() {\n return chartFontColor(this.props.currentColor);\n }\n resetColor() {\n this.props.onColorPicked(\"\");\n }\n setCustomColor(ev) {\n if (!isColorValid(this.state.currentColor)) {\n ev.stopPropagation();\n this.state.isCurrentColorInvalid = true;\n return;\n }\n const color = toHex(this.state.currentColor);\n this.state.isCurrentColorInvalid = false;\n this.props.onColorPicked(color);\n this.state.currentColor = color;\n }\n toggleColorPicker() {\n this.state.showGradient = !this.state.showGradient;\n }\n computeCustomColor(ev) {\n this.state.isCurrentColorInvalid = false;\n this.state.currentColor = computeCustomColor(ev);\n }\n hideMagnifier(_ev) {\n this.state.style.display = \"none\";\n }\n showMagnifier(_ev) {\n this.state.style.display = \"block\";\n }\n moveMagnifier(ev) {\n this.state.style.background = computeCustomColor(ev);\n const shiftFromCursor = 10;\n this.state.style.left = `${ev.offsetX + shiftFromCursor}px`;\n this.state.style.top = `${ev.offsetY + shiftFromCursor}px`;\n }\n get magnifyingGlassStyle() {\n const { display, background, left, top } = this.state.style;\n return `display:${display};${display === \"block\" ? `background-color:${background};left:${left};top:${top};` : \"\"}`;\n }\n }\n ColorPicker.template = \"o-spreadsheet-ColorPicker\";\n ColorPicker.defaultProps = {\n currentColor: \"\", //TODO Change it to false instead of empty string\n };\n ColorPicker.props = {\n dropdownDirection: { type: String, optional: true },\n onColorPicked: Function,\n currentColor: { type: String, optional: true },\n maxHeight: { type: Number, optional: true },\n };\n\n class LineBarPieDesignPanel extends owl.Component {\n constructor() {\n super(...arguments);\n this.state = owl.useState({\n fillColorTool: false,\n });\n }\n onClick(ev) {\n this.state.fillColorTool = false;\n }\n setup() {\n owl.useExternalListener(window, \"click\", this.onClick);\n }\n toggleColorPicker() {\n this.state.fillColorTool = !this.state.fillColorTool;\n }\n updateBackgroundColor(color) {\n this.props.updateChart({\n background: color,\n });\n }\n updateTitle(ev) {\n this.props.updateChart({\n title: ev.target.value,\n });\n }\n updateSelect(attr, ev) {\n this.props.updateChart({\n [attr]: ev.target.value,\n });\n }\n }\n LineBarPieDesignPanel.template = \"o-spreadsheet-LineBarPieDesignPanel\";\n LineBarPieDesignPanel.components = { ColorPicker };\n LineBarPieDesignPanel.props = {\n figureId: String,\n definition: Object,\n updateChart: Function,\n };\n\n class BarChartDesignPanel extends LineBarPieDesignPanel {\n }\n BarChartDesignPanel.template = \"o-spreadsheet-BarChartDesignPanel\";\n\n class GaugeChartConfigPanel extends owl.Component {\n constructor() {\n super(...arguments);\n this.state = owl.useState({\n dataRangeDispatchResult: undefined,\n });\n this.dataRange = this.props.definition.dataRange;\n }\n get configurationErrorMessages() {\n var _a;\n const cancelledReasons = [...(((_a = this.state.dataRangeDispatchResult) === null || _a === void 0 ? void 0 : _a.reasons) || [])];\n return cancelledReasons.map((error) => ChartTerms.Errors[error] || ChartTerms.Errors.Unexpected);\n }\n get isDataRangeInvalid() {\n var _a;\n return !!((_a = this.state.dataRangeDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(36 /* CommandResult.InvalidGaugeDataRange */));\n }\n onDataRangeChanged(ranges) {\n this.dataRange = ranges[0];\n }\n updateDataRange() {\n this.state.dataRangeDispatchResult = this.props.updateChart({\n dataRange: this.dataRange,\n });\n }\n }\n GaugeChartConfigPanel.template = \"o-spreadsheet-GaugeChartConfigPanel\";\n GaugeChartConfigPanel.components = { SelectionInput };\n GaugeChartConfigPanel.props = {\n figureId: String,\n definition: Object,\n updateChart: Function,\n };\n\n css /* scss */ `\n .o-gauge-color-set {\n .o-gauge-color-set-color-button {\n display: inline-block;\n border: 1px solid #dadce0;\n border-radius: 4px;\n cursor: pointer;\n padding: 1px 2px;\n }\n .o-gauge-color-set-color-button:hover {\n background-color: rgba(0, 0, 0, 0.08);\n }\n table {\n table-layout: fixed;\n margin-top: 2%;\n display: table;\n text-align: left;\n font-size: 12px;\n line-height: 18px;\n width: 100%;\n }\n th.o-gauge-color-set-colorPicker {\n width: 8%;\n }\n th.o-gauge-color-set-text {\n width: 40%;\n }\n th.o-gauge-color-set-value {\n width: 22%;\n }\n th.o-gauge-color-set-type {\n width: 30%;\n }\n input,\n select {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n }\n }\n`;\n class GaugeChartDesignPanel extends owl.Component {\n constructor() {\n super(...arguments);\n this.state = owl.useState({\n openedMenu: undefined,\n sectionRuleDispatchResult: undefined,\n });\n }\n get designErrorMessages() {\n var _a;\n const cancelledReasons = [...(((_a = this.state.sectionRuleDispatchResult) === null || _a === void 0 ? void 0 : _a.reasons) || [])];\n return cancelledReasons.map((error) => ChartTerms.Errors[error] || ChartTerms.Errors.Unexpected);\n }\n updateBackgroundColor(color) {\n this.state.openedMenu = undefined;\n this.props.updateChart({\n background: color,\n });\n }\n updateTitle(ev) {\n this.props.updateChart({\n title: ev.target.value,\n });\n }\n isRangeMinInvalid() {\n var _a, _b, _c;\n return !!(((_a = this.state.sectionRuleDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(37 /* CommandResult.EmptyGaugeRangeMin */)) ||\n ((_b = this.state.sectionRuleDispatchResult) === null || _b === void 0 ? void 0 : _b.isCancelledBecause(38 /* CommandResult.GaugeRangeMinNaN */)) ||\n ((_c = this.state.sectionRuleDispatchResult) === null || _c === void 0 ? void 0 : _c.isCancelledBecause(41 /* CommandResult.GaugeRangeMinBiggerThanRangeMax */)));\n }\n isRangeMaxInvalid() {\n var _a, _b, _c;\n return !!(((_a = this.state.sectionRuleDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(39 /* CommandResult.EmptyGaugeRangeMax */)) ||\n ((_b = this.state.sectionRuleDispatchResult) === null || _b === void 0 ? void 0 : _b.isCancelledBecause(40 /* CommandResult.GaugeRangeMaxNaN */)) ||\n ((_c = this.state.sectionRuleDispatchResult) === null || _c === void 0 ? void 0 : _c.isCancelledBecause(41 /* CommandResult.GaugeRangeMinBiggerThanRangeMax */)));\n }\n // ---------------------------------------------------------------------------\n // COLOR_SECTION_TEMPLATE\n // ---------------------------------------------------------------------------\n get isLowerInflectionPointInvalid() {\n var _a, _b;\n return !!(((_a = this.state.sectionRuleDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(42 /* CommandResult.GaugeLowerInflectionPointNaN */)) ||\n ((_b = this.state.sectionRuleDispatchResult) === null || _b === void 0 ? void 0 : _b.isCancelledBecause(44 /* CommandResult.GaugeLowerBiggerThanUpper */)));\n }\n get isUpperInflectionPointInvalid() {\n var _a, _b;\n return !!(((_a = this.state.sectionRuleDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(43 /* CommandResult.GaugeUpperInflectionPointNaN */)) ||\n ((_b = this.state.sectionRuleDispatchResult) === null || _b === void 0 ? void 0 : _b.isCancelledBecause(44 /* CommandResult.GaugeLowerBiggerThanUpper */)));\n }\n updateInflectionPointValue(attr, ev) {\n const sectionRule = deepCopy(this.props.definition.sectionRule);\n sectionRule[attr].value = ev.target.value;\n this.updateSectionRule(sectionRule);\n }\n updateInflectionPointType(attr, ev) {\n const sectionRule = deepCopy(this.props.definition.sectionRule);\n sectionRule[attr].type = ev.target.value;\n this.updateSectionRule(sectionRule);\n }\n updateSectionColor(target, color) {\n const sectionRule = deepCopy(this.props.definition.sectionRule);\n sectionRule.colors[target] = color;\n this.updateSectionRule(sectionRule);\n this.closeMenus();\n }\n updateRangeMin(ev) {\n let sectionRule = deepCopy(this.props.definition.sectionRule);\n sectionRule = {\n ...sectionRule,\n rangeMin: ev.target.value,\n };\n this.updateSectionRule(sectionRule);\n }\n updateRangeMax(ev) {\n let sectionRule = deepCopy(this.props.definition.sectionRule);\n sectionRule = {\n ...sectionRule,\n rangeMax: ev.target.value,\n };\n this.updateSectionRule(sectionRule);\n }\n toggleMenu(menu) {\n const isSelected = this.state.openedMenu === menu;\n this.closeMenus();\n if (!isSelected) {\n this.state.openedMenu = menu;\n }\n }\n updateSectionRule(sectionRule) {\n this.state.sectionRuleDispatchResult = this.props.updateChart({\n sectionRule,\n });\n }\n closeMenus() {\n this.state.openedMenu = undefined;\n }\n }\n GaugeChartDesignPanel.template = \"o-spreadsheet-GaugeChartDesignPanel\";\n GaugeChartDesignPanel.components = { ColorPicker };\n GaugeChartDesignPanel.props = {\n figureId: String,\n definition: Object,\n updateChart: Function,\n };\n\n class LineConfigPanel extends LineBarPieConfigPanel {\n get canTreatLabelsAsText() {\n const chart = this.env.model.getters.getChart(this.props.figureId);\n if (chart && chart instanceof LineChart) {\n return canChartParseLabels(chart.labelRange, this.env.model.getters);\n }\n return false;\n }\n onUpdateLabelsAsText(ev) {\n this.props.updateChart({\n labelsAsText: ev.target.checked,\n });\n }\n onUpdateStacked(ev) {\n this.props.updateChart({\n stacked: ev.target.checked,\n });\n }\n onUpdateAggregated(ev) {\n this.props.updateChart({\n aggregated: ev.target.checked,\n });\n }\n }\n LineConfigPanel.template = \"o-spreadsheet-LineConfigPanel\";\n\n class LineChartDesignPanel extends LineBarPieDesignPanel {\n }\n LineChartDesignPanel.template = \"o-spreadsheet-LineChartDesignPanel\";\n\n class ScorecardChartConfigPanel extends owl.Component {\n constructor() {\n super(...arguments);\n this.state = owl.useState({\n keyValueDispatchResult: undefined,\n baselineDispatchResult: undefined,\n });\n this.keyValue = this.props.definition.keyValue;\n this.baseline = this.props.definition.baseline;\n }\n get errorMessages() {\n var _a, _b;\n const cancelledReasons = [\n ...(((_a = this.state.keyValueDispatchResult) === null || _a === void 0 ? void 0 : _a.reasons) || []),\n ...(((_b = this.state.baselineDispatchResult) === null || _b === void 0 ? void 0 : _b.reasons) || []),\n ];\n return cancelledReasons.map((error) => ChartTerms.Errors[error] || ChartTerms.Errors.Unexpected);\n }\n get isKeyValueInvalid() {\n var _a;\n return !!((_a = this.state.keyValueDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(34 /* CommandResult.InvalidScorecardKeyValue */));\n }\n get isBaselineInvalid() {\n var _a;\n return !!((_a = this.state.keyValueDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(35 /* CommandResult.InvalidScorecardBaseline */));\n }\n onKeyValueRangeChanged(ranges) {\n this.keyValue = ranges[0];\n }\n updateKeyValueRange() {\n this.state.keyValueDispatchResult = this.props.updateChart({\n keyValue: this.keyValue,\n });\n }\n onBaselineRangeChanged(ranges) {\n this.baseline = ranges[0];\n }\n updateBaselineRange() {\n this.state.baselineDispatchResult = this.props.updateChart({\n baseline: this.baseline,\n });\n }\n updateBaselineMode(ev) {\n this.props.updateChart({ baselineMode: ev.target.value });\n }\n }\n ScorecardChartConfigPanel.template = \"o-spreadsheet-ScorecardChartConfigPanel\";\n ScorecardChartConfigPanel.components = { SelectionInput };\n ScorecardChartConfigPanel.props = {\n figureId: String,\n definition: Object,\n updateChart: Function,\n };\n\n class ScorecardChartDesignPanel extends owl.Component {\n constructor() {\n super(...arguments);\n this.state = owl.useState({\n openedColorPicker: undefined,\n });\n }\n updateTitle(ev) {\n this.props.updateChart({\n title: ev.target.value,\n });\n }\n updateBaselineDescr(ev) {\n this.props.updateChart({ baselineDescr: ev.target.value });\n }\n openColorPicker(colorPickerId) {\n this.state.openedColorPicker = colorPickerId;\n }\n setColor(color, colorPickerId) {\n switch (colorPickerId) {\n case \"backgroundColor\":\n this.props.updateChart({ background: color });\n break;\n case \"baselineColorDown\":\n this.props.updateChart({ baselineColorDown: color });\n break;\n case \"baselineColorUp\":\n this.props.updateChart({ baselineColorUp: color });\n break;\n }\n this.state.openedColorPicker = undefined;\n }\n }\n ScorecardChartDesignPanel.template = \"o-spreadsheet-ScorecardChartDesignPanel\";\n ScorecardChartDesignPanel.components = { ColorPicker };\n ScorecardChartDesignPanel.props = {\n figureId: String,\n definition: Object,\n updateChart: Function,\n };\n\n const chartSidePanelComponentRegistry = new Registry();\n chartSidePanelComponentRegistry\n .add(\"line\", {\n configuration: LineConfigPanel,\n design: LineChartDesignPanel,\n })\n .add(\"bar\", {\n configuration: BarConfigPanel,\n design: BarChartDesignPanel,\n })\n .add(\"pie\", {\n configuration: LineBarPieConfigPanel,\n design: LineBarPieDesignPanel,\n })\n .add(\"gauge\", {\n configuration: GaugeChartConfigPanel,\n design: GaugeChartDesignPanel,\n })\n .add(\"scorecard\", {\n configuration: ScorecardChartConfigPanel,\n design: ScorecardChartDesignPanel,\n });\n\n css /* scss */ `\n .o-chart {\n .o-panel {\n display: flex;\n .o-panel-element {\n flex: 1 0 auto;\n padding: 8px 0px;\n text-align: center;\n cursor: pointer;\n border-right: 1px solid darkgray;\n &.inactive {\n background-color: ${BACKGROUND_HEADER_COLOR};\n border-bottom: 1px solid darkgray;\n }\n .fa {\n margin-right: 4px;\n }\n }\n .o-panel-element:last-child {\n border-right: none;\n }\n }\n\n .o-with-color-picker {\n position: relative;\n }\n .o-with-color-picker > span {\n border-bottom: 4px solid;\n }\n }\n`;\n class ChartPanel extends owl.Component {\n constructor() {\n super(...arguments);\n this.shouldUpdateChart = true;\n }\n get figureId() {\n return this.state.figureId;\n }\n get sheetId() {\n return this.state.sheetId;\n }\n setup() {\n const selectedFigureId = this.env.model.getters.getSelectedFigureId();\n if (!selectedFigureId) {\n throw new Error(_lt(\"Cannot open the chart side panel while no chart are selected\"));\n }\n this.state = owl.useState({\n panel: \"configuration\",\n figureId: selectedFigureId,\n sheetId: this.env.model.getters.getActiveSheetId(),\n });\n owl.onWillUpdateProps(() => {\n const selectedFigureId = this.env.model.getters.getSelectedFigureId();\n if (selectedFigureId && selectedFigureId !== this.state.figureId) {\n this.state.figureId = selectedFigureId;\n this.state.sheetId = this.env.model.getters.getActiveSheetId();\n this.shouldUpdateChart = false;\n }\n else {\n this.shouldUpdateChart = true;\n }\n if (!this.env.model.getters.isChartDefined(this.figureId)) {\n this.props.onCloseSidePanel();\n return;\n }\n });\n }\n updateChart(updateDefinition) {\n if (!this.shouldUpdateChart) {\n return;\n }\n const definition = {\n ...this.getChartDefinition(),\n ...updateDefinition,\n };\n return this.env.model.dispatch(\"UPDATE_CHART\", {\n definition,\n id: this.figureId,\n sheetId: this.sheetId,\n });\n }\n onTypeChange(type) {\n const context = this.env.model.getters.getContextCreationChart(this.figureId);\n if (!context) {\n throw new Error(\"Chart not defined.\");\n }\n const definition = getChartDefinitionFromContextCreation(context, type);\n this.env.model.dispatch(\"UPDATE_CHART\", {\n definition,\n id: this.figureId,\n sheetId: this.sheetId,\n });\n }\n get chartPanel() {\n const type = this.env.model.getters.getChartType(this.figureId);\n if (!type) {\n throw new Error(\"Chart not defined.\");\n }\n const chartPanel = chartSidePanelComponentRegistry.get(type);\n if (!chartPanel) {\n throw new Error(`Component is not defined for type ${type}`);\n }\n return chartPanel;\n }\n getChartDefinition(figureId = this.figureId) {\n return this.env.model.getters.getChartDefinition(figureId);\n }\n get chartTypes() {\n return getChartTypes();\n }\n activatePanel(panel) {\n this.state.panel = panel;\n }\n }\n ChartPanel.template = \"o-spreadsheet-ChartPanel\";\n ChartPanel.props = {\n onCloseSidePanel: Function,\n };\n\n // -----------------------------------------------------------------------------\n // We need here the svg of the icons that we need to convert to images for the renderer\n // -----------------------------------------------------------------------------\n const ARROW_DOWN = '';\n const ARROW_UP = '';\n const ARROW_RIGHT = '';\n const SMILE = '';\n const MEH = '';\n const FROWN = '';\n const GREEN_DOT = '';\n const YELLOW_DOT = '';\n const RED_DOT = '';\n function loadIconImage(svg) {\n /** We have to add xmlns, as it's not added by owl in the canvas */\n svg = ` c.id === rules[0]);\n if (cf) {\n this.editConditionalFormat(cf);\n }\n }\n owl.onWillUpdateProps((nextProps) => {\n const newActiveSheetId = this.env.model.getters.getActiveSheetId();\n if (newActiveSheetId !== this.activeSheetId) {\n this.activeSheetId = newActiveSheetId;\n this.switchToList();\n }\n else if (nextProps.selection !== this.props.selection) {\n const sheetId = this.env.model.getters.getActiveSheetId();\n const rules = this.env.model.getters.getRulesSelection(sheetId, nextProps.selection || []);\n if (rules.length === 1) {\n const cf = this.conditionalFormats.find((c) => c.id === rules[0]);\n if (cf) {\n this.editConditionalFormat(cf);\n }\n }\n else {\n this.switchToList();\n }\n }\n });\n owl.useExternalListener(window, \"click\", this.closeMenus);\n }\n get conditionalFormats() {\n return this.env.model.getters.getConditionalFormats(this.env.model.getters.getActiveSheetId());\n }\n get isRangeValid() {\n return this.state.errors.includes(24 /* CommandResult.EmptyRange */);\n }\n errorMessage(error) {\n return CfTerms.Errors[error] || CfTerms.Errors.Unexpected;\n }\n /**\n * Switch to the list view\n */\n switchToList() {\n this.state.mode = \"list\";\n this.state.currentCF = undefined;\n this.state.currentCFType = undefined;\n this.state.errors = [];\n this.state.rules = this.getDefaultRules();\n }\n getStyle(rule) {\n if (rule.type === \"CellIsRule\") {\n const fontWeight = rule.style.bold ? \"bold\" : \"normal\";\n const fontDecoration = getTextDecoration(rule.style);\n const fontStyle = rule.style.italic ? \"italic\" : \"normal\";\n const color = rule.style.textColor || \"none\";\n const backgroundColor = rule.style.fillColor || \"none\";\n return `font-weight:${fontWeight};\n text-decoration:${fontDecoration};\n font-style:${fontStyle};\n color:${color};\n background-color:${backgroundColor};`;\n }\n else if (rule.type === \"ColorScaleRule\") {\n const minColor = colorNumberString(rule.minimum.color);\n const midColor = rule.midpoint ? colorNumberString(rule.midpoint.color) : null;\n const maxColor = colorNumberString(rule.maximum.color);\n const baseString = \"background-image: linear-gradient(to right, \";\n return midColor\n ? baseString + minColor + \", \" + midColor + \", \" + maxColor + \")\"\n : baseString + minColor + \", \" + maxColor + \")\";\n }\n return \"\";\n }\n getDescription(cf) {\n switch (cf.rule.type) {\n case \"CellIsRule\":\n const description = CellIsOperators[cf.rule.operator];\n if (cf.rule.values.length === 1) {\n return `${description} ${cf.rule.values[0]}`;\n }\n if (cf.rule.values.length === 2) {\n return _t(\"%s %s and %s\", description, cf.rule.values[0], cf.rule.values[1]);\n }\n return description;\n case \"ColorScaleRule\":\n return CfTerms.ColorScale;\n case \"IconSetRule\":\n return CfTerms.IconSet;\n default:\n return \"\";\n }\n }\n saveConditionalFormat() {\n if (this.state.currentCF) {\n const invalidRanges = this.state.currentCF.ranges.some((xc) => !xc.match(rangeReference));\n if (invalidRanges) {\n this.state.errors = [25 /* CommandResult.InvalidRange */];\n return;\n }\n const sheetId = this.env.model.getters.getActiveSheetId();\n const result = this.env.model.dispatch(\"ADD_CONDITIONAL_FORMAT\", {\n cf: {\n rule: this.getEditorRule(),\n id: this.state.mode === \"edit\"\n ? this.state.currentCF.id\n : this.env.model.uuidGenerator.uuidv4(),\n },\n ranges: this.state.currentCF.ranges.map((xc) => this.env.model.getters.getRangeDataFromXc(sheetId, xc)),\n sheetId,\n });\n if (!result.isSuccessful) {\n this.state.errors = result.reasons;\n }\n else {\n this.switchToList();\n }\n }\n }\n /**\n * Get the rule currently edited with the editor\n */\n getEditorRule() {\n switch (this.state.currentCFType) {\n case \"CellIsRule\":\n return this.state.rules.cellIs;\n case \"ColorScaleRule\":\n return this.state.rules.colorScale;\n case \"IconSetRule\":\n return this.state.rules.iconSet;\n }\n throw new Error(`Invalid cf type: ${this.state.currentCFType}`);\n }\n getDefaultRules() {\n return {\n cellIs: {\n type: \"CellIsRule\",\n operator: \"IsNotEmpty\",\n values: [],\n style: { fillColor: \"#b6d7a8\" },\n },\n colorScale: {\n type: \"ColorScaleRule\",\n minimum: { type: \"value\", color: 0xffffff },\n midpoint: undefined,\n maximum: { type: \"value\", color: 0x6aa84f },\n },\n iconSet: {\n type: \"IconSetRule\",\n icons: {\n upper: \"arrowGood\",\n middle: \"arrowNeutral\",\n lower: \"arrowBad\",\n },\n upperInflectionPoint: {\n type: \"percentage\",\n value: \"66\",\n operator: \"gt\",\n },\n lowerInflectionPoint: {\n type: \"percentage\",\n value: \"33\",\n operator: \"gt\",\n },\n },\n };\n }\n /**\n * Create a new CF, a CellIsRule by default\n */\n addConditionalFormat() {\n this.state.mode = \"add\";\n this.state.currentCFType = \"CellIsRule\";\n this.state.currentCF = {\n id: this.env.model.uuidGenerator.uuidv4(),\n ranges: this.env.model.getters\n .getSelectedZones()\n .map((zone) => this.env.model.getters.zoneToXC(this.env.model.getters.getActiveSheetId(), zone)),\n };\n }\n /**\n * Delete a CF\n */\n deleteConditionalFormat(cf) {\n this.env.model.dispatch(\"REMOVE_CONDITIONAL_FORMAT\", {\n id: cf.id,\n sheetId: this.env.model.getters.getActiveSheetId(),\n });\n }\n /**\n * Edit an existing CF. Return without doing anything in reorder mode.\n */\n editConditionalFormat(cf) {\n if (this.state.mode === \"reorder\")\n return;\n this.state.mode = \"edit\";\n this.state.currentCF = cf;\n this.state.currentCFType = cf.rule.type;\n switch (cf.rule.type) {\n case \"CellIsRule\":\n this.state.rules.cellIs = cf.rule;\n break;\n case \"ColorScaleRule\":\n this.state.rules.colorScale = cf.rule;\n break;\n case \"IconSetRule\":\n this.state.rules.iconSet = cf.rule;\n break;\n }\n }\n /**\n * Reorder existing CFs\n */\n reorderConditionalFormats() {\n this.state.mode = \"reorder\";\n }\n reorderRule(cf, direction) {\n this.env.model.dispatch(\"MOVE_CONDITIONAL_FORMAT\", {\n cfId: cf.id,\n direction: direction,\n sheetId: this.env.model.getters.getActiveSheetId(),\n });\n }\n changeRuleType(ruleType) {\n if (this.state.currentCFType === ruleType || !this.state.rules) {\n return;\n }\n this.state.errors = [];\n this.state.currentCFType = ruleType;\n }\n onRangesChanged(ranges) {\n if (this.state.currentCF) {\n this.state.currentCF.ranges = ranges;\n }\n }\n /*****************************************************************************\n * Common\n ****************************************************************************/\n toggleMenu(menu) {\n const isSelected = this.state.openedMenu === menu;\n this.closeMenus();\n if (!isSelected) {\n this.state.openedMenu = menu;\n }\n }\n closeMenus() {\n this.state.openedMenu = undefined;\n }\n /*****************************************************************************\n * Cell Is Rule\n ****************************************************************************/\n get isValue1Invalid() {\n var _a;\n return !!((_a = this.state.errors) === null || _a === void 0 ? void 0 : _a.includes(51 /* CommandResult.FirstArgMissing */));\n }\n get isValue2Invalid() {\n var _a;\n return !!((_a = this.state.errors) === null || _a === void 0 ? void 0 : _a.includes(52 /* CommandResult.SecondArgMissing */));\n }\n toggleStyle(tool) {\n const style = this.state.rules.cellIs.style;\n style[tool] = !style[tool];\n this.closeMenus();\n }\n setColor(target, color) {\n this.state.rules.cellIs.style[target] = color;\n this.closeMenus();\n }\n /*****************************************************************************\n * Color Scale Rule\n ****************************************************************************/\n isValueInvalid(threshold) {\n switch (threshold) {\n case \"minimum\":\n return (this.state.errors.includes(58 /* CommandResult.MinInvalidFormula */) ||\n this.state.errors.includes(50 /* CommandResult.MinBiggerThanMid */) ||\n this.state.errors.includes(47 /* CommandResult.MinBiggerThanMax */) ||\n this.state.errors.includes(53 /* CommandResult.MinNaN */));\n case \"midpoint\":\n return (this.state.errors.includes(59 /* CommandResult.MidInvalidFormula */) ||\n this.state.errors.includes(54 /* CommandResult.MidNaN */) ||\n this.state.errors.includes(49 /* CommandResult.MidBiggerThanMax */));\n case \"maximum\":\n return (this.state.errors.includes(60 /* CommandResult.MaxInvalidFormula */) ||\n this.state.errors.includes(55 /* CommandResult.MaxNaN */));\n default:\n return false;\n }\n }\n setColorScaleColor(target, color) {\n const point = this.state.rules.colorScale[target];\n if (point) {\n point.color = Number.parseInt(color.substr(1), 16);\n }\n this.closeMenus();\n }\n getPreviewGradient() {\n var _a;\n const rule = this.state.rules.colorScale;\n const minColor = colorNumberString(rule.minimum.color);\n const midColor = colorNumberString(((_a = rule.midpoint) === null || _a === void 0 ? void 0 : _a.color) || DEFAULT_COLOR_SCALE_MIDPOINT_COLOR);\n const maxColor = colorNumberString(rule.maximum.color);\n const baseString = \"background-image: linear-gradient(to right, \";\n return rule.midpoint === undefined\n ? baseString + minColor + \", \" + maxColor + \")\"\n : baseString + minColor + \", \" + midColor + \", \" + maxColor + \")\";\n }\n getThresholdColor(threshold) {\n return threshold\n ? colorNumberString(threshold.color)\n : colorNumberString(DEFAULT_COLOR_SCALE_MIDPOINT_COLOR);\n }\n onMidpointChange(ev) {\n const type = ev.target.value;\n const rule = this.state.rules.colorScale;\n if (type === \"none\") {\n rule.midpoint = undefined;\n }\n else {\n rule.midpoint = {\n color: DEFAULT_COLOR_SCALE_MIDPOINT_COLOR,\n value: \"\",\n ...rule.midpoint,\n type,\n };\n }\n }\n /*****************************************************************************\n * Icon Set\n ****************************************************************************/\n isInflectionPointInvalid(inflectionPoint) {\n switch (inflectionPoint) {\n case \"lowerInflectionPoint\":\n return (this.state.errors.includes(57 /* CommandResult.ValueLowerInflectionNaN */) ||\n this.state.errors.includes(62 /* CommandResult.ValueLowerInvalidFormula */) ||\n this.state.errors.includes(48 /* CommandResult.LowerBiggerThanUpper */));\n case \"upperInflectionPoint\":\n return (this.state.errors.includes(56 /* CommandResult.ValueUpperInflectionNaN */) ||\n this.state.errors.includes(61 /* CommandResult.ValueUpperInvalidFormula */) ||\n this.state.errors.includes(48 /* CommandResult.LowerBiggerThanUpper */));\n default:\n return true;\n }\n }\n reverseIcons() {\n const icons = this.state.rules.iconSet.icons;\n const upper = icons.upper;\n icons.upper = icons.lower;\n icons.lower = upper;\n }\n setIconSet(iconSet) {\n const icons = this.state.rules.iconSet.icons;\n icons.upper = this.iconSets[iconSet].good;\n icons.middle = this.iconSets[iconSet].neutral;\n icons.lower = this.iconSets[iconSet].bad;\n }\n setIcon(target, icon) {\n this.state.rules.iconSet.icons[target] = icon;\n }\n }\n ConditionalFormattingPanel.template = \"o-spreadsheet-ConditionalFormattingPanel\";\n ConditionalFormattingPanel.components = { SelectionInput, IconPicker, ColorPicker };\n ConditionalFormattingPanel.props = {\n selection: { type: Object, optional: true },\n onCloseSidePanel: Function,\n };\n\n css /* scss */ `\n .o-custom-currency {\n .o-format-proposals {\n color: black;\n }\n }\n`;\n class CustomCurrencyPanel extends owl.Component {\n setup() {\n this.availableCurrencies = [];\n this.state = owl.useState({\n selectedCurrencyIndex: 0,\n currencyCode: \"\",\n currencySymbol: \"\",\n selectedFormatIndex: 0,\n });\n owl.onWillStart(() => this.updateAvailableCurrencies());\n }\n get formatProposals() {\n const currency = this.availableCurrencies[this.state.selectedCurrencyIndex];\n const proposalBases = this.initProposalBases(currency.decimalPlaces);\n const firstPosition = currency.position;\n const secondPosition = currency.position === \"before\" ? \"after\" : \"before\";\n const symbol = this.state.currencySymbol.trim() ? this.state.currencySymbol : \"\";\n const code = this.state.currencyCode.trim() ? this.state.currencyCode : \"\";\n return code || symbol\n ? [\n ...this.createFormatProposals(proposalBases, symbol, code, firstPosition),\n ...this.createFormatProposals(proposalBases, symbol, code, secondPosition),\n ]\n : [];\n }\n get isSameFormat() {\n const selectedFormat = this.formatProposals[this.state.selectedFormatIndex];\n return selectedFormat ? selectedFormat.format === this.getCommonFormat() : false;\n }\n async updateAvailableCurrencies() {\n var _a, _b;\n if (currenciesRegistry.getAll().length === 0) {\n const currencies = (await ((_b = (_a = this.env).loadCurrencies) === null || _b === void 0 ? void 0 : _b.call(_a))) || [];\n currencies.forEach((currency, index) => {\n currenciesRegistry.add(index.toString(), currency);\n });\n }\n const emptyCurrency = {\n name: this.env._t(CustomCurrencyTerms.Custom),\n code: \"\",\n symbol: \"\",\n decimalPlaces: 2,\n position: \"after\",\n };\n this.availableCurrencies = [emptyCurrency, ...currenciesRegistry.getAll()];\n }\n updateSelectCurrency(ev) {\n const target = ev.target;\n this.state.selectedCurrencyIndex = parseInt(target.value, 10);\n const currency = this.availableCurrencies[this.state.selectedCurrencyIndex];\n this.state.currencyCode = currency.code;\n this.state.currencySymbol = currency.symbol;\n }\n updateCode(ev) {\n const target = ev.target;\n this.state.currencyCode = target.value;\n this.initAvailableCurrencies();\n }\n updateSymbol(ev) {\n const target = ev.target;\n this.state.currencySymbol = target.value;\n this.initAvailableCurrencies();\n }\n updateSelectFormat(ev) {\n const target = ev.target;\n this.state.selectedFormatIndex = parseInt(target.value, 10);\n }\n apply() {\n const selectedFormat = this.formatProposals[this.state.selectedFormatIndex];\n this.env.model.dispatch(\"SET_FORMATTING\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n target: this.env.model.getters.getSelectedZones(),\n format: selectedFormat.format,\n });\n }\n // ---------------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------------\n initAvailableCurrencies() {\n this.state.selectedCurrencyIndex = 0;\n }\n initProposalBases(decimalPlaces) {\n const result = [{ format: \"#,##0\", example: \"1,000\" }];\n const decimalRepresentation = decimalPlaces ? \".\" + \"0\".repeat(decimalPlaces) : \"\";\n if (decimalRepresentation) {\n result.push({\n format: \"#,##0\" + decimalRepresentation,\n example: \"1,000\" + decimalRepresentation,\n });\n }\n return result;\n }\n createFormatProposals(proposalBases, symbol, code, position) {\n let formatProposals = [];\n // 1 - add proposal with symbol and without code\n if (symbol) {\n for (let base of proposalBases) {\n formatProposals.push(this.createFormatProposal(position, base.example, base.format, symbol));\n }\n }\n // 2 - if code exist --> add more proposal with symbol and with code\n if (code) {\n for (let base of proposalBases) {\n const expression = (position === \"after\" ? \" \" : \"\") + code + \" \" + symbol;\n formatProposals.push(this.createFormatProposal(position, base.example, base.format, expression));\n }\n }\n return formatProposals;\n }\n createFormatProposal(position, baseExample, formatBase, expression) {\n const formatExpression = \"[$\" + expression + \"]\";\n return {\n example: position === \"before\" ? expression + baseExample : baseExample + expression,\n format: position === \"before\" ? formatExpression + formatBase : formatBase + formatExpression,\n };\n }\n getCommonFormat() {\n const selectedZones = this.env.model.getters.getSelectedZones();\n const sheetId = this.env.model.getters.getActiveSheetId();\n const cells = selectedZones\n .map((zone) => this.env.model.getters.getEvaluatedCellsInZone(sheetId, zone))\n .flat();\n const firstFormat = cells[0].format;\n return cells.every((cell) => cell.format === firstFormat) ? firstFormat : undefined;\n }\n currencyDisplayName(currency) {\n return currency.name + (currency.code ? ` (${currency.code})` : \"\");\n }\n }\n CustomCurrencyPanel.template = \"o-spreadsheet-CustomCurrencyPanel\";\n CustomCurrencyPanel.props = {\n onCloseSidePanel: Function,\n };\n\n css /* scss */ `\n .o-find-and-replace {\n .o-far-item {\n display: block;\n .o-far-checkbox {\n display: inline-block;\n .o-far-input {\n vertical-align: middle;\n }\n .o-far-label {\n position: relative;\n top: 1.5px;\n padding-left: 4px;\n }\n }\n }\n outline: none;\n height: 100%;\n .o-input-search-container {\n display: flex;\n .o-input-with-count {\n flex-grow: 1;\n width: auto;\n }\n .o-input-without-count {\n width: 100%;\n }\n .o-input-count {\n width: fit-content;\n padding: 4 0 4 4;\n }\n }\n }\n`;\n class FindAndReplacePanel extends owl.Component {\n constructor() {\n super(...arguments);\n this.state = owl.useState(this.initialState());\n this.showFormulaState = false;\n this.findAndReplaceRef = owl.useRef(\"findAndReplace\");\n }\n get hasSearchResult() {\n return this.env.model.getters.getCurrentSelectedMatchIndex() !== null;\n }\n get pendingSearch() {\n return this.debounceTimeoutId !== undefined;\n }\n setup() {\n this.showFormulaState = this.env.model.getters.shouldShowFormulas();\n owl.onMounted(() => this.focusInput());\n owl.onWillUnmount(() => {\n this.env.model.dispatch(\"CLEAR_SEARCH\");\n this.env.model.dispatch(\"SET_FORMULA_VISIBILITY\", { show: this.showFormulaState });\n });\n }\n onInput(ev) {\n this.state.toSearch = ev.target.value;\n this.debouncedUpdateSearch();\n }\n onKeydownSearch(ev) {\n if (ev.key === \"Enter\") {\n ev.preventDefault();\n ev.stopPropagation();\n this.onSelectNextCell();\n }\n }\n onKeydownReplace(ev) {\n if (ev.key === \"Enter\") {\n ev.preventDefault();\n ev.stopPropagation();\n this.replace();\n }\n }\n onFocusSidePanel() {\n this.state.searchOptions.searchFormulas = this.env.model.getters.shouldShowFormulas();\n this.env.model.dispatch(\"REFRESH_SEARCH\");\n }\n searchFormulas() {\n this.env.model.dispatch(\"SET_FORMULA_VISIBILITY\", {\n show: this.state.searchOptions.searchFormulas,\n });\n this.updateSearch();\n }\n onSelectPreviousCell() {\n this.env.model.dispatch(\"SELECT_SEARCH_PREVIOUS_MATCH\");\n }\n onSelectNextCell() {\n this.env.model.dispatch(\"SELECT_SEARCH_NEXT_MATCH\");\n }\n updateSearch() {\n this.env.model.dispatch(\"UPDATE_SEARCH\", {\n toSearch: this.state.toSearch,\n searchOptions: this.state.searchOptions,\n });\n }\n debouncedUpdateSearch() {\n clearTimeout(this.debounceTimeoutId);\n this.debounceTimeoutId = setTimeout(() => {\n this.updateSearch();\n this.debounceTimeoutId = undefined;\n }, 400);\n }\n replace() {\n this.env.model.dispatch(\"REPLACE_SEARCH\", {\n replaceWith: this.state.replaceWith,\n });\n }\n replaceAll() {\n this.env.model.dispatch(\"REPLACE_ALL_SEARCH\", {\n replaceWith: this.state.replaceWith,\n });\n }\n // ---------------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------------\n focusInput() {\n const el = this.findAndReplaceRef.el;\n const input = el.querySelector(`input`);\n if (input) {\n input.focus();\n }\n }\n initialState() {\n return {\n toSearch: \"\",\n replaceWith: \"\",\n searchOptions: {\n matchCase: false,\n exactMatch: false,\n searchFormulas: false,\n },\n };\n }\n }\n FindAndReplacePanel.template = \"o-spreadsheet-FindAndReplacePanel\";\n FindAndReplacePanel.props = {\n onCloseSidePanel: Function,\n };\n\n const sidePanelRegistry = new Registry();\n sidePanelRegistry.add(\"ConditionalFormatting\", {\n title: _lt(\"Conditional formatting\"),\n Body: ConditionalFormattingPanel,\n });\n sidePanelRegistry.add(\"ChartPanel\", {\n title: _lt(\"Chart\"),\n Body: ChartPanel,\n });\n sidePanelRegistry.add(\"FindAndReplace\", {\n title: _lt(\"Find and Replace\"),\n Body: FindAndReplacePanel,\n });\n sidePanelRegistry.add(\"CustomCurrency\", {\n title: _lt(\"Custom currency format\"),\n Body: CustomCurrencyPanel,\n });\n\n class TopBarComponentRegistry extends Registry {\n constructor() {\n super(...arguments);\n this.mapping = {};\n this.uuidGenerator = new UuidGenerator();\n }\n add(name, value) {\n const component = { ...value, id: this.uuidGenerator.uuidv4() };\n return super.add(name, component);\n }\n }\n const topbarComponentRegistry = new TopBarComponentRegistry();\n\n // -----------------------------------------------------------------------------\n // STYLE\n // -----------------------------------------------------------------------------\n const ANCHOR_SIZE = 8;\n const BORDER_WIDTH = 1;\n const ACTIVE_BORDER_WIDTH = 2;\n css /*SCSS*/ `\n div.o-figure {\n box-sizing: border-box;\n position: absolute;\n width: 100%;\n height: 100%;\n user-select: none;\n\n &:focus {\n outline: none;\n }\n }\n\n div.o-figure-border {\n box-sizing: border-box;\n z-index: 1;\n }\n\n .o-figure-wrapper {\n position: absolute;\n box-sizing: content-box;\n\n .o-fig-anchor {\n z-index: ${ComponentsImportance.ChartAnchor};\n position: absolute;\n width: ${ANCHOR_SIZE}px;\n height: ${ANCHOR_SIZE}px;\n background-color: #1a73e8;\n outline: ${BORDER_WIDTH}px solid white;\n\n &.o-top {\n cursor: n-resize;\n }\n &.o-topRight {\n cursor: ne-resize;\n }\n &.o-right {\n cursor: e-resize;\n }\n &.o-bottomRight {\n cursor: se-resize;\n }\n &.o-bottom {\n cursor: s-resize;\n }\n &.o-bottomLeft {\n cursor: sw-resize;\n }\n &.o-left {\n cursor: w-resize;\n }\n &.o-topLeft {\n cursor: nw-resize;\n }\n }\n\n .o-figure-menu {\n right: 0px;\n display: none;\n position: absolute;\n padding: 5px;\n }\n\n .o-figure-menu-item {\n cursor: pointer;\n }\n\n .o-figure.active:focus,\n .o-figure:hover {\n .o-figure-menu {\n display: flex;\n }\n }\n }\n`;\n class FigureComponent extends owl.Component {\n constructor() {\n super(...arguments);\n this.figureRef = owl.useRef(\"figure\");\n }\n get isSelected() {\n return this.env.model.getters.getSelectedFigureId() === this.props.figure.id;\n }\n get figureRegistry() {\n return figureRegistry;\n }\n getBorderWidth() {\n if (this.env.isDashboard())\n return 0;\n return this.isSelected ? ACTIVE_BORDER_WIDTH : this.borderWidth;\n }\n get borderStyle() {\n const borderWidth = this.getBorderWidth();\n const borderColor = this.isSelected ? SELECTION_BORDER_COLOR : FIGURE_BORDER_COLOR;\n return `border: ${borderWidth}px solid ${borderColor};`;\n }\n get wrapperStyle() {\n const { x, y, width, height } = this.props.figure;\n return cssPropertiesToCss({\n left: `${x}px`,\n top: `${y}px`,\n width: `${width}px`,\n height: `${height}px`,\n \"z-index\": String(ComponentsImportance.Figure + (this.isSelected ? 1 : 0)),\n });\n }\n getResizerPosition(resizer) {\n const anchorCenteringOffset = (ANCHOR_SIZE - ACTIVE_BORDER_WIDTH) / 2;\n let style = \"\";\n if (resizer.includes(\"top\")) {\n style += `top: ${-anchorCenteringOffset}px;`;\n }\n else if (resizer.includes(\"bottom\")) {\n style += `bottom: ${-anchorCenteringOffset}px;`;\n }\n else {\n style += ` bottom: calc(50% - ${anchorCenteringOffset}px);`;\n }\n if (resizer.includes(\"left\")) {\n style += `left: ${-anchorCenteringOffset}px;`;\n }\n else if (resizer.includes(\"right\")) {\n style += `right: ${-anchorCenteringOffset}px;`;\n }\n else {\n style += ` right: calc(50% - ${anchorCenteringOffset}px);`;\n }\n return style;\n }\n setup() {\n const borderWidth = figureRegistry.get(this.props.figure.tag).borderWidth;\n this.borderWidth = borderWidth !== undefined ? borderWidth : BORDER_WIDTH;\n owl.useEffect((selectedFigureId, thisFigureId, el) => {\n if (selectedFigureId === thisFigureId) {\n /** Scrolling on a newly inserted figure that overflows outside the viewport\n * will break the whole layout.\n * NOTE: `preventScroll`does not work on mobile but then again,\n * mobile is not really supported ATM.\n *\n * TODO: When implementing proper mobile, we will need to scroll the viewport\n * correctly (and render?) before focusing the element.\n */\n el === null || el === void 0 ? void 0 : el.focus({ preventScroll: true });\n }\n }, () => [this.env.model.getters.getSelectedFigureId(), this.props.figure.id, this.figureRef.el]);\n }\n clickAnchor(dirX, dirY, ev) {\n this.props.onClickAnchor(dirX, dirY, ev);\n }\n onMouseDown(ev) {\n this.props.onMouseDown(ev);\n }\n onKeyDown(ev) {\n const figure = this.props.figure;\n switch (ev.key) {\n case \"Delete\":\n this.env.model.dispatch(\"DELETE_FIGURE\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n id: figure.id,\n });\n this.props.onFigureDeleted();\n ev.stopPropagation();\n ev.preventDefault();\n break;\n case \"ArrowDown\":\n case \"ArrowLeft\":\n case \"ArrowRight\":\n case \"ArrowUp\":\n const deltaMap = {\n ArrowDown: [0, 1],\n ArrowLeft: [-1, 0],\n ArrowRight: [1, 0],\n ArrowUp: [0, -1],\n };\n const delta = deltaMap[ev.key];\n this.env.model.dispatch(\"UPDATE_FIGURE\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n id: figure.id,\n x: figure.x + delta[0],\n y: figure.y + delta[1],\n });\n ev.stopPropagation();\n ev.preventDefault();\n break;\n }\n }\n }\n FigureComponent.template = \"o-spreadsheet-FigureComponent\";\n FigureComponent.components = {};\n FigureComponent.defaultProps = {\n onFigureDeleted: () => { },\n onMouseDown: () => { },\n onClickAnchor: () => { },\n };\n FigureComponent.props = {\n figure: Object,\n style: { type: String, optional: true },\n onFigureDeleted: { type: Function, optional: true },\n onMouseDown: { type: Function, optional: true },\n onClickAnchor: { type: Function, optional: true },\n };\n\n /**\n * Each figure \u2b50 is positioned inside a container `div` placed and sized\n * according to the split pane the figure is part of, or a separate container for the figure\n * currently drag & dropped. Any part of the figure outside of the container is hidden\n * thanks to its `overflow: hidden` property.\n *\n * Additionally, the figure is placed inside a \"inverse viewport\" `div` \ud83d\udfe5.\n * Its position represents the viewport position in the grid: its top/left\n * corner represents the top/left corner of the grid.\n *\n * It allows to position the figure inside this div regardless of the\n * (possibly freezed) viewports and the scrolling position.\n *\n * --: container limits\n * \ud83d\udfe5: inverse viewport\n * \u2b50: figure top/left position\n *\n * container\n * \u2193\n * |\ud83d\udfe5--------------------------------------------\n * | \\ |\n * | \\ |\n * | \\ |\n * | \\ visible area | no scroll\n * | \u2b50 |\n * | |\n * | |\n * -----------------------------------------------\n *\n * the scrolling of the pane is applied as an inverse offset\n * to the div which will in turn move the figure up and down\n * inside the container.\n * Hence, once the figure position is (resp. partly) out of\n * the container dimensions, it will be (resp. partly) hidden.\n *\n * The same reasoning applies to the horizontal axis.\n *\n * \ud83d\udfe5 \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\n * \\ \u2191\n * \\ |\n * \\ | inverse viewport = -1 * scroll of pane\n * \\ |\n * \u2b50 <- not visible |\n * \u2193\n * -----------------------------------------------\n * | |\n * | |\n * | |\n * | visible area |\n * | |\n * | |\n * | |\n * -----------------------------------------------\n *\n * In the case the d&d figure container, the container is the same as the \"topLeft\" container for\n * frozen pane (unaffected by scroll and always visible). The figure coordinates are transformed\n * for this container at the start of the d&d, and transformed back at the end to adapt to the scroll\n * that occurred during the drag & drop, and to position the figure on the correct pane.\n *\n */\n class FiguresContainer extends owl.Component {\n constructor() {\n super(...arguments);\n this.dnd = owl.useState({\n figId: undefined,\n x: 0,\n y: 0,\n width: 0,\n height: 0,\n });\n }\n setup() {\n owl.onMounted(() => {\n // horrible, but necessary\n // the following line ensures that we render the figures with the correct\n // viewport. The reason is that whenever we initialize the grid\n // component, we do not know yet the actual size of the viewport, so the\n // first owl rendering is done with an empty viewport. Only then we can\n // compute which figures should be displayed, so we have to force a\n // new rendering\n this.render();\n });\n }\n getVisibleFigures() {\n const visibleFigures = this.env.model.getters.getVisibleFigures();\n if (this.dnd.figId && !visibleFigures.some((figure) => figure.id === this.dnd.figId)) {\n visibleFigures.push(this.env.model.getters.getFigure(this.env.model.getters.getActiveSheetId(), this.dnd.figId));\n }\n return visibleFigures;\n }\n get containers() {\n const visibleFigures = this.getVisibleFigures();\n const containers = [];\n for (const containerType of [\n \"topLeft\",\n \"topRight\",\n \"bottomLeft\",\n \"bottomRight\",\n ]) {\n const containerFigures = visibleFigures.filter((figure) => this.getFigureContainer(figure) === containerType);\n if (containerFigures.length > 0) {\n containers.push({\n type: containerType,\n figures: containerFigures,\n style: this.getContainerStyle(containerType),\n inverseViewportStyle: this.getInverseViewportPositionStyle(containerType),\n });\n }\n }\n if (this.dnd.figId) {\n containers.push({\n type: \"dnd\",\n figures: [this.getDndFigure()],\n style: this.getContainerStyle(\"dnd\"),\n inverseViewportStyle: this.getInverseViewportPositionStyle(\"dnd\"),\n });\n }\n return containers;\n }\n getContainerStyle(container) {\n const { width: viewWidth, height: viewHeight } = this.env.model.getters.getMainViewportRect();\n const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();\n const left = [\"bottomRight\", \"topRight\"].includes(container) ? viewportX : 0;\n const width = viewWidth - left;\n const top = [\"bottomRight\", \"bottomLeft\"].includes(container) ? viewportY : 0;\n const height = viewHeight - top;\n return cssPropertiesToCss({\n left: `${left}px`,\n top: `${top}px`,\n width: `${width}px`,\n height: `${height}px`,\n });\n }\n getInverseViewportPositionStyle(container) {\n const { scrollX, scrollY } = this.env.model.getters.getActiveSheetScrollInfo();\n const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();\n const left = [\"bottomRight\", \"topRight\"].includes(container) ? -(viewportX + scrollX) : 0;\n const top = [\"bottomRight\", \"bottomLeft\"].includes(container) ? -(viewportY + scrollY) : 0;\n return cssPropertiesToCss({\n left: `${left}px`,\n top: `${top}px`,\n });\n }\n getFigureContainer(figure) {\n const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();\n if (figure.id === this.dnd.figId) {\n return \"dnd\";\n }\n else if (figure.x < viewportX && figure.y < viewportY) {\n return \"topLeft\";\n }\n else if (figure.x < viewportX) {\n return \"bottomLeft\";\n }\n else if (figure.y < viewportY) {\n return \"topRight\";\n }\n else {\n return \"bottomRight\";\n }\n }\n startDraggingFigure(figure, ev) {\n if (ev.button > 0 || this.env.model.getters.isReadonly()) {\n // not main button, probably a context menu and no d&d in readonly mode\n return;\n }\n const selectResult = this.env.model.dispatch(\"SELECT_FIGURE\", { id: figure.id });\n if (!selectResult.isSuccessful) {\n return;\n }\n const sheetId = this.env.model.getters.getActiveSheetId();\n const mouseInitialX = ev.clientX;\n const mouseInitialY = ev.clientY;\n const { x: dndInitialX, y: dndInitialY } = this.internalToScreenCoordinates(figure);\n this.dnd.x = dndInitialX;\n this.dnd.y = dndInitialY;\n this.dnd.width = figure.width;\n this.dnd.height = figure.height;\n const onMouseMove = (ev) => {\n const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();\n const { scrollX, scrollY } = this.env.model.getters.getActiveSheetScrollInfo();\n const minX = viewportX ? 0 : -scrollX;\n const minY = viewportY ? 0 : -scrollY;\n this.dnd.figId = figure.id;\n const newX = ev.clientX;\n let deltaX = newX - mouseInitialX;\n this.dnd.x = Math.max(dndInitialX + deltaX, minX);\n const newY = ev.clientY;\n let deltaY = newY - mouseInitialY;\n this.dnd.y = Math.max(dndInitialY + deltaY, minY);\n };\n const onMouseUp = (ev) => {\n if (!this.dnd.figId) {\n return;\n }\n let { x, y } = this.screenCoordinatesToInternal(this.dnd);\n this.dnd.figId = undefined;\n this.env.model.dispatch(\"UPDATE_FIGURE\", { sheetId, id: figure.id, x, y });\n };\n startDnd(onMouseMove, onMouseUp);\n }\n startResize(figure, dirX, dirY, ev) {\n ev.stopPropagation();\n const initialX = ev.clientX;\n const initialY = ev.clientY;\n const { x: dndInitialX, y: dndInitialY } = this.internalToScreenCoordinates(figure);\n this.dnd.x = dndInitialX;\n this.dnd.y = dndInitialY;\n this.dnd.width = figure.width;\n this.dnd.height = figure.height;\n const keepRatio = figureRegistry.get(figure.tag).keepRatio || false;\n const minFigSize = figureRegistry.get(figure.tag).minFigSize || MIN_FIG_SIZE;\n let onMouseMove;\n if (keepRatio && dirX != 0 && dirY != 0) {\n onMouseMove = (ev) => {\n this.dnd.figId = figure.id;\n const deltaX = Math.min(dirX * (initialX - ev.clientX), figure.width - minFigSize);\n const deltaY = Math.min(dirY * (initialY - ev.clientY), figure.height - minFigSize);\n const fraction = Math.min(deltaX / figure.width, deltaY / figure.height);\n this.dnd.width = figure.width * (1 - fraction);\n this.dnd.height = figure.height * (1 - fraction);\n if (dirX < 0) {\n this.dnd.x = dndInitialX + figure.width * fraction;\n }\n if (dirY < 0) {\n this.dnd.y = dndInitialY + figure.height * fraction;\n }\n };\n }\n else {\n onMouseMove = (ev) => {\n this.dnd.figId = figure.id;\n const deltaX = Math.max(dirX * (ev.clientX - initialX), minFigSize - figure.width);\n const deltaY = Math.max(dirY * (ev.clientY - initialY), minFigSize - figure.height);\n this.dnd.width = figure.width + deltaX;\n this.dnd.height = figure.height + deltaY;\n if (dirX < 0) {\n this.dnd.x = dndInitialX - deltaX;\n }\n if (dirY < 0) {\n this.dnd.y = dndInitialY - deltaY;\n }\n };\n }\n const onMouseUp = (ev) => {\n if (!this.dnd.figId) {\n return;\n }\n this.dnd.figId = undefined;\n let { x, y } = this.screenCoordinatesToInternal(this.dnd);\n const update = { x, y };\n if (dirX) {\n update.width = this.dnd.width;\n }\n if (dirY) {\n update.height = this.dnd.height;\n }\n this.env.model.dispatch(\"UPDATE_FIGURE\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n id: figure.id,\n ...update,\n });\n };\n startDnd(onMouseMove, onMouseUp);\n }\n getDndFigure() {\n const figure = this.getVisibleFigures().find((fig) => fig.id === this.dnd.figId);\n if (!figure)\n throw new Error(\"Dnd figure not found\");\n return {\n ...figure,\n x: this.dnd.x,\n y: this.dnd.y,\n width: this.dnd.width,\n height: this.dnd.height,\n };\n }\n getFigureStyle(figure) {\n if (figure.id !== this.dnd.figId)\n return \"\";\n return cssPropertiesToCss({\n opacity: \"0.9\",\n cursor: \"grabbing\",\n });\n }\n internalToScreenCoordinates({ x, y }) {\n const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();\n const { scrollX, scrollY } = this.env.model.getters.getActiveSheetScrollInfo();\n x = x < viewportX ? x : x - scrollX;\n y = y < viewportY ? y : y - scrollY;\n return { x, y };\n }\n screenCoordinatesToInternal({ x, y }) {\n const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();\n const { scrollX, scrollY } = this.env.model.getters.getActiveSheetScrollInfo();\n x = viewportX && x < viewportX ? x : x + scrollX;\n y = viewportY && y < viewportY ? y : y + scrollY;\n return { x, y };\n }\n }\n FiguresContainer.template = \"o-spreadsheet-FiguresContainer\";\n FiguresContainer.components = { FigureComponent };\n FiguresContainer.props = {\n onFigureDeleted: Function,\n };\n\n /**\n * Repeatedly calls a callback function with a time delay between calls.\n */\n function useInterval(callback, delay) {\n let intervalId;\n const { setInterval, clearInterval } = window;\n owl.useEffect(() => {\n intervalId = setInterval(callback, delay);\n return () => clearInterval(intervalId);\n }, () => [delay]);\n return {\n pause: () => {\n clearInterval(intervalId);\n intervalId = undefined;\n },\n resume: () => {\n if (intervalId === undefined) {\n intervalId = setInterval(callback, delay);\n }\n },\n };\n }\n\n function useCellHovered(env, gridRef, callback) {\n let hoveredPosition = {\n col: undefined,\n row: undefined,\n };\n const { Date } = window;\n let x = 0;\n let y = 0;\n let lastMoved = 0;\n function getPosition() {\n const col = env.model.getters.getColIndex(x);\n const row = env.model.getters.getRowIndex(y);\n return { col, row };\n }\n const { pause, resume } = useInterval(checkTiming, 200);\n function checkTiming() {\n const { col, row } = getPosition();\n const delta = Date.now() - lastMoved;\n if (delta > 300 && (col !== hoveredPosition.col || row !== hoveredPosition.row)) {\n setPosition(undefined, undefined);\n }\n if (delta > 300) {\n if (col < 0 || row < 0) {\n return;\n }\n setPosition(col, row);\n }\n }\n function updateMousePosition(e) {\n x = e.offsetX;\n y = e.offsetY;\n lastMoved = Date.now();\n }\n function recompute() {\n const { col, row } = getPosition();\n if (col !== hoveredPosition.col || row !== hoveredPosition.row) {\n setPosition(undefined, undefined);\n }\n }\n owl.onMounted(() => {\n const grid = gridRef.el;\n grid.addEventListener(\"mousemove\", updateMousePosition);\n grid.addEventListener(\"mouseleave\", pause);\n grid.addEventListener(\"mouseenter\", resume);\n grid.addEventListener(\"mousedown\", recompute);\n });\n owl.onWillUnmount(() => {\n const grid = gridRef.el;\n grid.removeEventListener(\"mousemove\", updateMousePosition);\n grid.removeEventListener(\"mouseleave\", pause);\n grid.removeEventListener(\"mouseenter\", resume);\n grid.removeEventListener(\"mousedown\", recompute);\n });\n function setPosition(col, row) {\n if (col !== hoveredPosition.col || row !== hoveredPosition.row) {\n hoveredPosition.col = col;\n hoveredPosition.row = row;\n callback({ col, row });\n }\n }\n return hoveredPosition;\n }\n function useTouchMove(gridRef, handler, canMoveUp) {\n let x = null;\n let y = null;\n function onTouchStart(ev) {\n if (ev.touches.length !== 1)\n return;\n x = ev.touches[0].clientX;\n y = ev.touches[0].clientY;\n }\n function onTouchEnd() {\n x = null;\n y = null;\n }\n function onTouchMove(ev) {\n if (ev.touches.length !== 1)\n return;\n // On mobile browsers, swiping down is often associated with \"pull to refresh\".\n // We only want this behavior if the grid is already at the top.\n // Otherwise we only want to move the canvas up, without triggering any refresh.\n if (canMoveUp()) {\n ev.preventDefault();\n ev.stopPropagation();\n }\n const currentX = ev.touches[0].clientX;\n const currentY = ev.touches[0].clientY;\n handler(x - currentX, y - currentY);\n x = currentX;\n y = currentY;\n }\n owl.onMounted(() => {\n gridRef.el.addEventListener(\"touchstart\", onTouchStart);\n gridRef.el.addEventListener(\"touchend\", onTouchEnd);\n gridRef.el.addEventListener(\"touchmove\", onTouchMove);\n });\n owl.onWillUnmount(() => {\n gridRef.el.removeEventListener(\"touchstart\", onTouchStart);\n gridRef.el.removeEventListener(\"touchend\", onTouchEnd);\n gridRef.el.removeEventListener(\"touchmove\", onTouchMove);\n });\n }\n class GridOverlay extends owl.Component {\n setup() {\n this.gridOverlay = owl.useRef(\"gridOverlay\");\n useCellHovered(this.env, this.gridOverlay, this.props.onCellHovered);\n const resizeObserver = new ResizeObserver(() => {\n this.props.onGridResized({\n height: this.gridOverlayEl.clientHeight,\n width: this.gridOverlayEl.clientWidth,\n });\n });\n owl.onMounted(() => {\n resizeObserver.observe(this.gridOverlayEl);\n });\n owl.onWillUnmount(() => {\n resizeObserver.disconnect();\n });\n useTouchMove(this.gridOverlay, this.props.onGridMoved, () => {\n const { scrollY } = this.env.model.getters.getActiveSheetDOMScrollInfo();\n return scrollY > 0;\n });\n }\n get gridOverlayEl() {\n if (!this.gridOverlay.el) {\n throw new Error(\"GridOverlay el is not defined.\");\n }\n return this.gridOverlay.el;\n }\n onMouseDown(ev) {\n if (ev.button > 0) {\n // not main button, probably a context menu\n return;\n }\n const [col, row] = this.getCartesianCoordinates(ev);\n this.props.onCellClicked(col, row, { shiftKey: ev.shiftKey, ctrlKey: ev.ctrlKey });\n }\n onDoubleClick(ev) {\n const [col, row] = this.getCartesianCoordinates(ev);\n this.props.onCellDoubleClicked(col, row);\n }\n onContextMenu(ev) {\n ev.preventDefault();\n const [col, row] = this.getCartesianCoordinates(ev);\n this.props.onCellRightClicked(col, row, { x: ev.clientX, y: ev.clientY });\n }\n getCartesianCoordinates(ev) {\n const colIndex = this.env.model.getters.getColIndex(ev.offsetX);\n const rowIndex = this.env.model.getters.getRowIndex(ev.offsetY);\n return [colIndex, rowIndex];\n }\n }\n GridOverlay.template = \"o-spreadsheet-GridOverlay\";\n GridOverlay.components = { FiguresContainer };\n GridOverlay.defaultProps = {\n onCellHovered: () => { },\n onCellDoubleClicked: () => { },\n onCellClicked: () => { },\n onCellRightClicked: () => { },\n onGridResized: () => { },\n onFigureDeleted: () => { },\n };\n GridOverlay.props = {\n onCellHovered: { type: Function, optional: true },\n onCellDoubleClicked: { type: Function, optional: true },\n onCellClicked: { type: Function, optional: true },\n onCellRightClicked: { type: Function, optional: true },\n onGridResized: { type: Function, optional: true },\n onFigureDeleted: { type: Function, optional: true },\n onGridMoved: Function,\n gridOverlayDimensions: String,\n };\n\n class GridPopover extends owl.Component {\n get cellPopover() {\n const popover = this.env.model.getters.getCellPopover(this.props.hoveredCell);\n if (!popover.isOpen) {\n return { isOpen: false };\n }\n const anchorRect = popover.anchorRect;\n return {\n ...popover,\n // transform from the \"canvas coordinate system\" to the \"body coordinate system\"\n anchorRect: {\n ...anchorRect,\n x: anchorRect.x + this.props.gridRect.x,\n y: anchorRect.y + this.props.gridRect.y,\n },\n };\n }\n }\n GridPopover.template = \"o-spreadsheet-GridPopover\";\n GridPopover.components = { Popover };\n GridPopover.props = {\n hoveredCell: Object,\n onClosePopover: Function,\n onMouseWheel: Function,\n gridRect: Object,\n };\n\n class AbstractResizer extends owl.Component {\n constructor() {\n super(...arguments);\n this.PADDING = 0;\n this.MAX_SIZE_MARGIN = 0;\n this.MIN_ELEMENT_SIZE = 0;\n this.lastSelectedElementIndex = null;\n this.state = owl.useState({\n resizerIsActive: false,\n isResizing: false,\n isMoving: false,\n isSelecting: false,\n waitingForMove: false,\n activeElement: 0,\n draggerLinePosition: 0,\n draggerShadowPosition: 0,\n draggerShadowThickness: 0,\n delta: 0,\n base: 0,\n });\n }\n _computeHandleDisplay(ev) {\n const position = this._getEvOffset(ev);\n const elementIndex = this._getElementIndex(position);\n if (elementIndex < 0) {\n return;\n }\n const dimensions = this._getDimensionsInViewport(elementIndex);\n if (position - dimensions.start < this.PADDING && elementIndex !== this._getViewportOffset()) {\n this.state.resizerIsActive = true;\n this.state.draggerLinePosition = dimensions.start;\n this.state.activeElement = this._getPreviousVisibleElement(elementIndex);\n }\n else if (dimensions.end - position < this.PADDING) {\n this.state.resizerIsActive = true;\n this.state.draggerLinePosition = dimensions.end;\n this.state.activeElement = elementIndex;\n }\n else {\n this.state.resizerIsActive = false;\n }\n }\n _computeGrabDisplay(ev) {\n const index = this._getElementIndex(this._getEvOffset(ev));\n const activeElements = this._getActiveElements();\n const selectedZoneStart = this._getSelectedZoneStart();\n const selectedZoneEnd = this._getSelectedZoneEnd();\n if (activeElements.has(selectedZoneStart)) {\n if (selectedZoneStart <= index && index <= selectedZoneEnd) {\n this.state.waitingForMove = true;\n return;\n }\n }\n this.state.waitingForMove = false;\n }\n onMouseMove(ev) {\n if (this.state.isResizing || this.state.isMoving || this.state.isSelecting) {\n return;\n }\n this._computeHandleDisplay(ev);\n this._computeGrabDisplay(ev);\n }\n onMouseLeave() {\n this.state.resizerIsActive = this.state.isResizing;\n this.state.waitingForMove = false;\n }\n onDblClick(ev) {\n this._fitElementSize(this.state.activeElement);\n this.state.isResizing = false;\n this._computeHandleDisplay(ev);\n this._computeGrabDisplay(ev);\n }\n onMouseDown(ev) {\n this.state.isResizing = true;\n this.state.delta = 0;\n const initialPosition = this._getClientPosition(ev);\n const styleValue = this.state.draggerLinePosition;\n const size = this._getElementSize(this.state.activeElement);\n const minSize = styleValue - size + this.MIN_ELEMENT_SIZE;\n const maxSize = this._getMaxSize();\n const onMouseUp = (ev) => {\n this.state.isResizing = false;\n if (this.state.delta !== 0) {\n this._updateSize();\n }\n };\n const onMouseMove = (ev) => {\n this.state.delta = this._getClientPosition(ev) - initialPosition;\n this.state.draggerLinePosition = styleValue + this.state.delta;\n if (this.state.draggerLinePosition < minSize) {\n this.state.draggerLinePosition = minSize;\n this.state.delta = this.MIN_ELEMENT_SIZE - size;\n }\n if (this.state.draggerLinePosition > maxSize) {\n this.state.draggerLinePosition = maxSize;\n this.state.delta = maxSize - styleValue;\n }\n };\n startDnd(onMouseMove, onMouseUp);\n }\n select(ev) {\n if (ev.button > 0) {\n // not main button, probably a context menu\n return;\n }\n const index = this._getElementIndex(this._getEvOffset(ev));\n if (index < 0) {\n return;\n }\n if (this.state.waitingForMove === true) {\n this.startMovement(ev);\n return;\n }\n if (this.env.model.getters.getEditionMode() === \"editing\") {\n this.env.model.selection.getBackToDefault();\n }\n this.startSelection(ev, index);\n }\n startMovement(ev) {\n this.state.waitingForMove = false;\n this.state.isMoving = true;\n const startDimensions = this._getDimensionsInViewport(this._getSelectedZoneStart());\n const endDimensions = this._getDimensionsInViewport(this._getSelectedZoneEnd());\n const defaultPosition = startDimensions.start;\n this.state.draggerLinePosition = defaultPosition;\n this.state.base = this._getSelectedZoneStart();\n this.state.draggerShadowPosition = defaultPosition;\n this.state.draggerShadowThickness = endDimensions.end - startDimensions.start;\n const mouseMoveMovement = (col, row) => {\n let elementIndex = this._getType() === \"COL\" ? col : row;\n if (elementIndex >= 0) {\n // define draggerLinePosition\n const dimensions = this._getDimensionsInViewport(elementIndex);\n if (elementIndex <= this._getSelectedZoneStart()) {\n this.state.draggerLinePosition = dimensions.start;\n this.state.draggerShadowPosition = dimensions.start;\n this.state.base = elementIndex;\n }\n else if (this._getSelectedZoneEnd() < elementIndex) {\n this.state.draggerLinePosition = dimensions.end;\n this.state.draggerShadowPosition = dimensions.end - this.state.draggerShadowThickness;\n this.state.base = elementIndex + 1;\n }\n else {\n this.state.draggerLinePosition = startDimensions.start;\n this.state.draggerShadowPosition = startDimensions.start;\n this.state.base = this._getSelectedZoneStart();\n }\n }\n };\n const mouseUpMovement = () => {\n this.state.isMoving = false;\n if (this.state.base !== this._getSelectedZoneStart()) {\n this._moveElements();\n }\n this._computeGrabDisplay(ev);\n };\n dragAndDropBeyondTheViewport(this.env, mouseMoveMovement, mouseUpMovement);\n }\n startSelection(ev, index) {\n this.state.isSelecting = true;\n if (ev.shiftKey) {\n this._increaseSelection(index);\n }\n else {\n this._selectElement(index, ev.ctrlKey);\n }\n this.lastSelectedElementIndex = index;\n const mouseMoveSelect = (col, row) => {\n let newIndex = this._getType() === \"COL\" ? col : row;\n if (newIndex !== this.lastSelectedElementIndex && newIndex !== -1) {\n this._increaseSelection(newIndex);\n this.lastSelectedElementIndex = newIndex;\n }\n };\n const mouseUpSelect = () => {\n this.state.isSelecting = false;\n this.lastSelectedElementIndex = null;\n this.env.model.dispatch(ev.ctrlKey ? \"PREPARE_SELECTION_INPUT_EXPANSION\" : \"STOP_SELECTION_INPUT\");\n this._computeGrabDisplay(ev);\n };\n dragAndDropBeyondTheViewport(this.env, mouseMoveSelect, mouseUpSelect);\n }\n onMouseUp(ev) {\n this.lastSelectedElementIndex = null;\n }\n onContextMenu(ev) {\n ev.preventDefault();\n const index = this._getElementIndex(this._getEvOffset(ev));\n if (index < 0)\n return;\n if (!this._getActiveElements().has(index)) {\n this._selectElement(index, false);\n }\n const type = this._getType();\n this.props.onOpenContextMenu(type, ev.clientX, ev.clientY);\n }\n }\n css /* scss */ `\n .o-col-resizer {\n position: absolute;\n top: 0;\n left: ${HEADER_WIDTH}px;\n right: 0;\n height: ${HEADER_HEIGHT}px;\n &.o-dragging {\n cursor: grabbing;\n }\n &.o-grab {\n cursor: grab;\n }\n .dragging-col-line {\n top: ${HEADER_HEIGHT}px;\n position: absolute;\n width: 2px;\n height: 10000px;\n background-color: black;\n }\n .dragging-col-shadow {\n top: ${HEADER_HEIGHT}px;\n position: absolute;\n height: 10000px;\n background-color: black;\n opacity: 0.1;\n }\n .o-handle {\n position: absolute;\n height: ${HEADER_HEIGHT}px;\n width: 4px;\n cursor: e-resize;\n background-color: ${SELECTION_BORDER_COLOR};\n }\n .dragging-resizer {\n top: ${HEADER_HEIGHT}px;\n position: absolute;\n margin-left: 2px;\n width: 1px;\n height: 10000px;\n background-color: ${SELECTION_BORDER_COLOR};\n }\n .o-unhide {\n width: ${UNHIDE_ICON_EDGE_LENGTH}px;\n height: ${UNHIDE_ICON_EDGE_LENGTH}px;\n position: absolute;\n overflow: hidden;\n border-radius: 2px;\n top: calc(${HEADER_HEIGHT}px / 2 - ${UNHIDE_ICON_EDGE_LENGTH}px / 2);\n }\n .o-unhide:hover {\n z-index: ${ComponentsImportance.Grid + 1};\n background-color: lightgrey;\n }\n .o-unhide > svg {\n position: relative;\n top: calc(${UNHIDE_ICON_EDGE_LENGTH}px / 2 - ${ICON_EDGE_LENGTH}px / 2);\n }\n }\n`;\n AbstractResizer.props = {\n onOpenContextMenu: Function,\n };\n class ColResizer extends AbstractResizer {\n setup() {\n super.setup();\n this.colResizerRef = owl.useRef(\"colResizer\");\n this.PADDING = 15;\n this.MAX_SIZE_MARGIN = 90;\n this.MIN_ELEMENT_SIZE = MIN_COL_WIDTH;\n }\n _getEvOffset(ev) {\n return ev.offsetX;\n }\n _getViewportOffset() {\n return this.env.model.getters.getActiveMainViewport().left;\n }\n _getClientPosition(ev) {\n return ev.clientX;\n }\n _getElementIndex(position) {\n return this.env.model.getters.getColIndex(position);\n }\n _getSelectedZoneStart() {\n return this.env.model.getters.getSelectedZone().left;\n }\n _getSelectedZoneEnd() {\n return this.env.model.getters.getSelectedZone().right;\n }\n _getEdgeScroll(position) {\n return this.env.model.getters.getEdgeScrollCol(position, position, position);\n }\n _getDimensionsInViewport(index) {\n return this.env.model.getters.getColDimensionsInViewport(this.env.model.getters.getActiveSheetId(), index);\n }\n _getElementSize(index) {\n return this.env.model.getters.getColSize(this.env.model.getters.getActiveSheetId(), index);\n }\n _getMaxSize() {\n return this.colResizerRef.el.clientWidth;\n }\n _updateSize() {\n const index = this.state.activeElement;\n const size = this.state.delta + this._getElementSize(index);\n const cols = this.env.model.getters.getActiveCols();\n this.env.model.dispatch(\"RESIZE_COLUMNS_ROWS\", {\n dimension: \"COL\",\n sheetId: this.env.model.getters.getActiveSheetId(),\n elements: cols.has(index) ? [...cols] : [index],\n size,\n });\n }\n _moveElements() {\n const elements = [];\n const start = this._getSelectedZoneStart();\n const end = this._getSelectedZoneEnd();\n for (let colIndex = start; colIndex <= end; colIndex++) {\n elements.push(colIndex);\n }\n const result = this.env.model.dispatch(\"MOVE_COLUMNS_ROWS\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n dimension: \"COL\",\n base: this.state.base,\n elements,\n });\n if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {\n this.env.raiseError(MergeErrorMessage);\n }\n }\n _selectElement(index, ctrlKey) {\n this.env.model.selection.selectColumn(index, ctrlKey ? \"newAnchor\" : \"overrideSelection\");\n }\n _increaseSelection(index) {\n this.env.model.selection.selectColumn(index, \"updateAnchor\");\n }\n _fitElementSize(index) {\n const cols = this.env.model.getters.getActiveCols();\n this.env.model.dispatch(\"AUTORESIZE_COLUMNS\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n cols: cols.has(index) ? [...cols] : [index],\n });\n }\n _getType() {\n return \"COL\";\n }\n _getActiveElements() {\n return this.env.model.getters.getActiveCols();\n }\n _getPreviousVisibleElement(index) {\n const sheetId = this.env.model.getters.getActiveSheetId();\n let row;\n for (row = index - 1; row >= 0; row--) {\n if (!this.env.model.getters.isColHidden(sheetId, row)) {\n break;\n }\n }\n return row;\n }\n unhide(hiddenElements) {\n this.env.model.dispatch(\"UNHIDE_COLUMNS_ROWS\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n elements: hiddenElements,\n dimension: \"COL\",\n });\n }\n unhideStyleValue(hiddenIndex) {\n return this._getDimensionsInViewport(hiddenIndex).start;\n }\n }\n ColResizer.template = \"o-spreadsheet-ColResizer\";\n css /* scss */ `\n .o-row-resizer {\n position: absolute;\n top: ${HEADER_HEIGHT}px;\n left: 0;\n right: 0;\n width: ${HEADER_WIDTH}px;\n height: 100%;\n &.o-dragging {\n cursor: grabbing;\n }\n &.o-grab {\n cursor: grab;\n }\n .dragging-row-line {\n left: ${HEADER_WIDTH}px;\n position: absolute;\n width: 10000px;\n height: 2px;\n background-color: black;\n }\n .dragging-row-shadow {\n left: ${HEADER_WIDTH}px;\n position: absolute;\n width: 10000px;\n background-color: black;\n opacity: 0.1;\n }\n .o-handle {\n position: absolute;\n height: 4px;\n width: ${HEADER_WIDTH}px;\n cursor: n-resize;\n background-color: ${SELECTION_BORDER_COLOR};\n }\n .dragging-resizer {\n left: ${HEADER_WIDTH}px;\n position: absolute;\n margin-top: 2px;\n width: 10000px;\n height: 1px;\n background-color: ${SELECTION_BORDER_COLOR};\n }\n .o-unhide {\n width: ${UNHIDE_ICON_EDGE_LENGTH}px;\n height: ${UNHIDE_ICON_EDGE_LENGTH}px;\n position: absolute;\n overflow: hidden;\n border-radius: 2px;\n left: calc(${HEADER_WIDTH}px - ${UNHIDE_ICON_EDGE_LENGTH}px - 2px);\n }\n .o-unhide > svg {\n position: relative;\n left: calc(${UNHIDE_ICON_EDGE_LENGTH}px / 2 - ${ICON_EDGE_LENGTH}px / 2);\n top: calc(${UNHIDE_ICON_EDGE_LENGTH}px / 2 - ${ICON_EDGE_LENGTH}px / 2);\n }\n .o-unhide:hover {\n z-index: ${ComponentsImportance.Grid + 1};\n background-color: lightgrey;\n }\n }\n`;\n ColResizer.props = {\n onOpenContextMenu: Function,\n };\n class RowResizer extends AbstractResizer {\n setup() {\n super.setup();\n this.rowResizerRef = owl.useRef(\"rowResizer\");\n this.PADDING = 5;\n this.MAX_SIZE_MARGIN = 60;\n this.MIN_ELEMENT_SIZE = MIN_ROW_HEIGHT;\n }\n _getEvOffset(ev) {\n return ev.offsetY;\n }\n _getViewportOffset() {\n return this.env.model.getters.getActiveMainViewport().top;\n }\n _getClientPosition(ev) {\n return ev.clientY;\n }\n _getElementIndex(position) {\n return this.env.model.getters.getRowIndex(position);\n }\n _getSelectedZoneStart() {\n return this.env.model.getters.getSelectedZone().top;\n }\n _getSelectedZoneEnd() {\n return this.env.model.getters.getSelectedZone().bottom;\n }\n _getEdgeScroll(position) {\n return this.env.model.getters.getEdgeScrollRow(position, position, position);\n }\n _getDimensionsInViewport(index) {\n return this.env.model.getters.getRowDimensionsInViewport(this.env.model.getters.getActiveSheetId(), index);\n }\n _getElementSize(index) {\n return this.env.model.getters.getRowSize(this.env.model.getters.getActiveSheetId(), index);\n }\n _getMaxSize() {\n return this.rowResizerRef.el.clientHeight;\n }\n _updateSize() {\n const index = this.state.activeElement;\n const size = this.state.delta + this._getElementSize(index);\n const rows = this.env.model.getters.getActiveRows();\n this.env.model.dispatch(\"RESIZE_COLUMNS_ROWS\", {\n dimension: \"ROW\",\n sheetId: this.env.model.getters.getActiveSheetId(),\n elements: rows.has(index) ? [...rows] : [index],\n size,\n });\n }\n _moveElements() {\n const elements = [];\n const start = this._getSelectedZoneStart();\n const end = this._getSelectedZoneEnd();\n for (let rowIndex = start; rowIndex <= end; rowIndex++) {\n elements.push(rowIndex);\n }\n const result = this.env.model.dispatch(\"MOVE_COLUMNS_ROWS\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n dimension: \"ROW\",\n base: this.state.base,\n elements,\n });\n if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {\n this.env.raiseError(MergeErrorMessage);\n }\n }\n _selectElement(index, ctrlKey) {\n this.env.model.selection.selectRow(index, ctrlKey ? \"newAnchor\" : \"overrideSelection\");\n }\n _increaseSelection(index) {\n this.env.model.selection.selectRow(index, \"updateAnchor\");\n }\n _fitElementSize(index) {\n const rows = this.env.model.getters.getActiveRows();\n this.env.model.dispatch(\"AUTORESIZE_ROWS\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n rows: rows.has(index) ? [...rows] : [index],\n });\n }\n _getType() {\n return \"ROW\";\n }\n _getActiveElements() {\n return this.env.model.getters.getActiveRows();\n }\n _getPreviousVisibleElement(index) {\n const sheetId = this.env.model.getters.getActiveSheetId();\n let row;\n for (row = index - 1; row >= 0; row--) {\n if (!this.env.model.getters.isRowHidden(sheetId, row)) {\n break;\n }\n }\n return row;\n }\n unhide(hiddenElements) {\n this.env.model.dispatch(\"UNHIDE_COLUMNS_ROWS\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n dimension: \"ROW\",\n elements: hiddenElements,\n });\n }\n unhideStyleValue(hiddenIndex) {\n return this._getDimensionsInViewport(hiddenIndex).start;\n }\n }\n RowResizer.template = \"o-spreadsheet-RowResizer\";\n css /* scss */ `\n .o-overlay {\n .all {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n width: ${HEADER_WIDTH}px;\n height: ${HEADER_HEIGHT}px;\n }\n }\n`;\n RowResizer.props = {\n onOpenContextMenu: Function,\n };\n class HeadersOverlay extends owl.Component {\n selectAll() {\n this.env.model.selection.selectAll();\n }\n }\n HeadersOverlay.template = \"o-spreadsheet-HeadersOverlay\";\n HeadersOverlay.components = { ColResizer, RowResizer };\n HeadersOverlay.props = {\n onOpenContextMenu: Function,\n };\n\n function useGridDrawing(refName, model, canvasSize) {\n const canvasRef = owl.useRef(refName);\n owl.useEffect(drawGrid);\n function drawGrid() {\n const canvas = canvasRef.el;\n const dpr = window.devicePixelRatio || 1;\n const ctx = canvas.getContext(\"2d\", { alpha: false });\n const thinLineWidth = 0.4 * dpr;\n const renderingContext = {\n ctx,\n dpr,\n thinLineWidth,\n };\n const { width, height } = canvasSize();\n canvas.style.width = `${width}px`;\n canvas.style.height = `${height}px`;\n canvas.width = width * dpr;\n canvas.height = height * dpr;\n canvas.setAttribute(\"style\", `width:${width}px;height:${height}px;`);\n // Imagine each pixel as a large square. The whole-number coordinates (0, 1, 2\u2026)\n // are the edges of the squares. If you draw a one-unit-wide line between whole-number\n // coordinates, it will overlap opposite sides of the pixel square, and the resulting\n // line will be drawn two pixels wide. To draw a line that is only one pixel wide,\n // you need to shift the coordinates by 0.5 perpendicular to the line's direction.\n // http://diveintohtml5.info/canvas.html#pixel-madness\n ctx.translate(-CANVAS_SHIFT, -CANVAS_SHIFT);\n ctx.scale(dpr, dpr);\n model.drawGrid(renderingContext);\n }\n }\n\n function useWheelHandler(handler) {\n function normalize(val, deltaMode) {\n return val * (deltaMode === 0 ? 1 : DEFAULT_CELL_HEIGHT);\n }\n const onMouseWheel = (ev) => {\n const deltaX = normalize(ev.shiftKey ? ev.deltaY : ev.deltaX, ev.deltaMode);\n const deltaY = normalize(ev.shiftKey ? ev.deltaX : ev.deltaY, ev.deltaMode);\n handler(deltaX, deltaY);\n };\n return onMouseWheel;\n }\n\n css /* scss */ `\n .o-border {\n position: absolute;\n &:hover {\n cursor: grab;\n }\n }\n .o-moving {\n cursor: grabbing;\n }\n`;\n class Border extends owl.Component {\n get style() {\n const isTop = [\"n\", \"w\", \"e\"].includes(this.props.orientation);\n const isLeft = [\"n\", \"w\", \"s\"].includes(this.props.orientation);\n const isHorizontal = [\"n\", \"s\"].includes(this.props.orientation);\n const isVertical = [\"w\", \"e\"].includes(this.props.orientation);\n const z = this.props.zone;\n const margin = 2;\n const rect = this.env.model.getters.getVisibleRect(z);\n const left = rect.x;\n const right = rect.x + rect.width - 2 * margin;\n const top = rect.y;\n const bottom = rect.y + rect.height - 2 * margin;\n const lineWidth = 4;\n const leftValue = isLeft ? left : right;\n const topValue = isTop ? top : bottom;\n const widthValue = isHorizontal ? right - left : lineWidth;\n const heightValue = isVertical ? bottom - top : lineWidth;\n return `\n left:${leftValue}px;\n top:${topValue}px;\n width:${widthValue}px;\n height:${heightValue}px;\n `;\n }\n onMouseDown(ev) {\n this.props.onMoveHighlight(ev.clientX, ev.clientY);\n }\n }\n Border.template = \"o-spreadsheet-Border\";\n Border.props = {\n zone: Object,\n orientation: String,\n isMoving: Boolean,\n onMoveHighlight: Function,\n };\n\n css /* scss */ `\n .o-corner {\n position: absolute;\n height: 6px;\n width: 6px;\n border: 1px solid white;\n }\n .o-corner-nw,\n .o-corner-se {\n &:hover {\n cursor: nwse-resize;\n }\n }\n .o-corner-ne,\n .o-corner-sw {\n &:hover {\n cursor: nesw-resize;\n }\n }\n .o-resizing {\n cursor: grabbing;\n }\n`;\n class Corner extends owl.Component {\n constructor() {\n super(...arguments);\n this.isTop = this.props.orientation[0] === \"n\";\n this.isLeft = this.props.orientation[1] === \"w\";\n }\n get style() {\n const z = this.props.zone;\n const col = this.isLeft ? z.left : z.right;\n const row = this.isTop ? z.top : z.bottom;\n const rect = this.env.model.getters.getVisibleRect({\n left: col,\n right: col,\n top: row,\n bottom: row,\n });\n // Don't show if not visible in the viewport\n if (rect.width * rect.height === 0) {\n return `display:none`;\n }\n const leftValue = this.isLeft ? rect.x : rect.x + rect.width;\n const topValue = this.isTop ? rect.y : rect.y + rect.height;\n return `\n left:${leftValue - AUTOFILL_EDGE_LENGTH / 2}px;\n top:${topValue - AUTOFILL_EDGE_LENGTH / 2}px;\n background-color:${this.props.color};\n `;\n }\n onMouseDown(ev) {\n this.props.onResizeHighlight(this.isLeft, this.isTop);\n }\n }\n Corner.template = \"o-spreadsheet-Corner\";\n Corner.props = {\n zone: Object,\n color: String,\n orientation: String,\n isResizing: Boolean,\n onResizeHighlight: Function,\n };\n\n css /*SCSS*/ `\n .o-highlight {\n z-index: ${ComponentsImportance.Highlight};\n }\n`;\n class Highlight extends owl.Component {\n constructor() {\n super(...arguments);\n this.highlightState = owl.useState({\n shiftingMode: \"none\",\n });\n }\n onResizeHighlight(isLeft, isTop) {\n const activeSheet = this.env.model.getters.getActiveSheet();\n this.highlightState.shiftingMode = \"isResizing\";\n const z = this.props.zone;\n const pivotCol = isLeft ? z.right : z.left;\n const pivotRow = isTop ? z.bottom : z.top;\n let lastCol = isLeft ? z.left : z.right;\n let lastRow = isTop ? z.top : z.bottom;\n let currentZone = z;\n this.env.model.dispatch(\"START_CHANGE_HIGHLIGHT\", {\n range: this.env.model.getters.getRangeDataFromZone(activeSheet.id, currentZone),\n });\n const mouseMove = (col, row) => {\n if (lastCol !== col || lastRow !== row) {\n const activeSheetId = this.env.model.getters.getActiveSheetId();\n lastCol = clip(col === -1 ? lastCol : col, 0, this.env.model.getters.getNumberCols(activeSheetId) - 1);\n lastRow = clip(row === -1 ? lastRow : row, 0, this.env.model.getters.getNumberRows(activeSheetId) - 1);\n let newZone = {\n left: Math.min(pivotCol, lastCol),\n top: Math.min(pivotRow, lastRow),\n right: Math.max(pivotCol, lastCol),\n bottom: Math.max(pivotRow, lastRow),\n };\n newZone = this.env.model.getters.expandZone(activeSheetId, newZone);\n if (!isEqual(newZone, currentZone)) {\n this.env.model.dispatch(\"CHANGE_HIGHLIGHT\", {\n range: this.env.model.getters.getRangeDataFromZone(activeSheet.id, newZone),\n });\n currentZone = newZone;\n }\n }\n };\n const mouseUp = () => {\n this.highlightState.shiftingMode = \"none\";\n // To do:\n // Command used here to restore focus to the current composer,\n // to be changed when refactoring the 'edition' plugin\n this.env.model.dispatch(\"STOP_COMPOSER_RANGE_SELECTION\");\n };\n dragAndDropBeyondTheViewport(this.env, mouseMove, mouseUp);\n }\n onMoveHighlight(clientX, clientY) {\n this.highlightState.shiftingMode = \"isMoving\";\n const z = this.props.zone;\n const position = gridOverlayPosition();\n const activeSheetId = this.env.model.getters.getActiveSheetId();\n const initCol = this.env.model.getters.getColIndex(clientX - position.left);\n const initRow = this.env.model.getters.getRowIndex(clientY - position.top);\n const deltaColMin = -z.left;\n const deltaColMax = this.env.model.getters.getNumberCols(activeSheetId) - z.right - 1;\n const deltaRowMin = -z.top;\n const deltaRowMax = this.env.model.getters.getNumberRows(activeSheetId) - z.bottom - 1;\n let currentZone = z;\n this.env.model.dispatch(\"START_CHANGE_HIGHLIGHT\", {\n range: this.env.model.getters.getRangeDataFromZone(activeSheetId, currentZone),\n });\n let lastCol = initCol;\n let lastRow = initRow;\n const mouseMove = (col, row) => {\n if (lastCol !== col || lastRow !== row) {\n lastCol = col === -1 ? lastCol : col;\n lastRow = row === -1 ? lastRow : row;\n const deltaCol = clip(lastCol - initCol, deltaColMin, deltaColMax);\n const deltaRow = clip(lastRow - initRow, deltaRowMin, deltaRowMax);\n let newZone = {\n left: z.left + deltaCol,\n top: z.top + deltaRow,\n right: z.right + deltaCol,\n bottom: z.bottom + deltaRow,\n };\n newZone = this.env.model.getters.expandZone(activeSheetId, newZone);\n if (!isEqual(newZone, currentZone)) {\n this.env.model.dispatch(\"CHANGE_HIGHLIGHT\", {\n range: this.env.model.getters.getRangeDataFromZone(activeSheetId, newZone),\n });\n currentZone = newZone;\n }\n }\n };\n const mouseUp = () => {\n this.highlightState.shiftingMode = \"none\";\n // To do:\n // Command used here to restore focus to the current composer,\n // to be changed when refactoring the 'edition' plugin\n this.env.model.dispatch(\"STOP_COMPOSER_RANGE_SELECTION\");\n };\n dragAndDropBeyondTheViewport(this.env, mouseMove, mouseUp);\n }\n }\n Highlight.template = \"o-spreadsheet-Highlight\";\n Highlight.components = {\n Corner,\n Border,\n };\n Highlight.props = {\n zone: Object,\n color: String,\n };\n\n class ScrollBar$1 {\n constructor(el, direction) {\n this.el = el;\n this.direction = direction;\n }\n get scroll() {\n return this.direction === \"horizontal\" ? this.el.scrollLeft : this.el.scrollTop;\n }\n set scroll(value) {\n if (this.direction === \"horizontal\") {\n this.el.scrollLeft = value;\n }\n else {\n this.el.scrollTop = value;\n }\n }\n }\n\n css /* scss */ `\n .o-scrollbar {\n position: absolute;\n overflow: auto;\n z-index: ${ComponentsImportance.ScrollBar};\n background-color: ${BACKGROUND_GRAY_COLOR};\n\n &.corner {\n right: 0px;\n bottom: 0px;\n height: ${SCROLLBAR_WIDTH$1}px;\n width: ${SCROLLBAR_WIDTH$1}px;\n border-top: 1px solid #e2e3e3;\n border-left: 1px solid #e2e3e3;\n }\n }\n`;\n class ScrollBar extends owl.Component {\n setup() {\n this.scrollbarRef = owl.useRef(\"scrollbar\");\n this.scrollbar = new ScrollBar$1(this.scrollbarRef.el, this.props.direction);\n owl.onMounted(() => {\n this.scrollbar.el = this.scrollbarRef.el;\n });\n // TODO improve useEffect dependencies typing in owl\n owl.useEffect(() => {\n if (this.scrollbar.scroll !== this.props.offset) {\n this.scrollbar.scroll = this.props.offset;\n }\n }, () => [this.scrollbar.scroll, this.props.offset]);\n }\n get sizeCss() {\n return cssPropertiesToCss({\n width: `${this.props.width}px`,\n height: `${this.props.height}px`,\n });\n }\n get positionCss() {\n return cssPropertiesToCss(this.props.position);\n }\n onScroll(ev) {\n if (this.props.offset !== this.scrollbar.scroll) {\n this.props.onScroll(this.scrollbar.scroll);\n }\n }\n }\n ScrollBar.template = owl.xml /*xml*/ `\n \n
\n
\n `;\n ScrollBar.defaultProps = {\n width: 1,\n height: 1,\n };\n ScrollBar.props = {\n width: { type: Number, optional: true },\n height: { type: Number, optional: true },\n direction: String,\n position: Object,\n offset: Number,\n onScroll: Function,\n };\n\n class HorizontalScrollBar extends owl.Component {\n get offset() {\n return this.env.model.getters.getActiveSheetDOMScrollInfo().scrollX;\n }\n get width() {\n return this.env.model.getters.getMainViewportRect().width;\n }\n get isDisplayed() {\n const { xRatio } = this.env.model.getters.getFrozenSheetViewRatio(this.env.model.getters.getActiveSheetId());\n return xRatio < 1;\n }\n get position() {\n const { x } = this.env.model.getters.getMainViewportRect();\n return {\n left: `${this.props.leftOffset + x}px`,\n bottom: \"0px\",\n height: `${SCROLLBAR_WIDTH$1}px`,\n right: `0px`,\n };\n }\n onScroll(offset) {\n const { scrollY } = this.env.model.getters.getActiveSheetDOMScrollInfo();\n this.env.model.dispatch(\"SET_VIEWPORT_OFFSET\", {\n offsetX: offset,\n offsetY: scrollY, // offsetY is the same\n });\n }\n }\n HorizontalScrollBar.components = { ScrollBar };\n HorizontalScrollBar.template = owl.xml /*xml*/ `\n `;\n HorizontalScrollBar.defaultProps = {\n leftOffset: 0,\n };\n HorizontalScrollBar.props = {\n leftOffset: { type: Number, optional: true },\n };\n\n class VerticalScrollBar extends owl.Component {\n get offset() {\n return this.env.model.getters.getActiveSheetDOMScrollInfo().scrollY;\n }\n get height() {\n return this.env.model.getters.getMainViewportRect().height;\n }\n get isDisplayed() {\n const { yRatio } = this.env.model.getters.getFrozenSheetViewRatio(this.env.model.getters.getActiveSheetId());\n return yRatio < 1;\n }\n get position() {\n const { y } = this.env.model.getters.getMainViewportRect();\n return {\n top: `${this.props.topOffset + y}px`,\n right: \"0px\",\n width: `${SCROLLBAR_WIDTH$1}px`,\n bottom: `0px`,\n };\n }\n onScroll(offset) {\n const { scrollX } = this.env.model.getters.getActiveSheetDOMScrollInfo();\n this.env.model.dispatch(\"SET_VIEWPORT_OFFSET\", {\n offsetX: scrollX,\n offsetY: offset,\n });\n }\n }\n VerticalScrollBar.components = { ScrollBar };\n VerticalScrollBar.template = owl.xml /*xml*/ `\n `;\n VerticalScrollBar.defaultProps = {\n topOffset: 0,\n };\n VerticalScrollBar.props = {\n topOffset: { type: Number, optional: true },\n };\n\n const registries$1 = {\n ROW: rowMenuRegistry,\n COL: colMenuRegistry,\n CELL: cellMenuRegistry,\n };\n // -----------------------------------------------------------------------------\n // JS\n // -----------------------------------------------------------------------------\n class Grid extends owl.Component {\n constructor() {\n super(...arguments);\n this.HEADER_HEIGHT = HEADER_HEIGHT;\n this.HEADER_WIDTH = HEADER_WIDTH;\n // this map will handle most of the actions that should happen on key down. The arrow keys are managed in the key\n // down itself\n this.keyDownMapping = {\n ENTER: () => {\n const cell = this.env.model.getters.getActiveCell();\n cell.type === CellValueType.empty\n ? this.props.onGridComposerCellFocused()\n : this.props.onComposerContentFocused();\n },\n TAB: () => this.env.model.selection.moveAnchorCell(\"right\", 1),\n \"SHIFT+TAB\": () => this.env.model.selection.moveAnchorCell(\"left\", 1),\n F2: () => {\n const cell = this.env.model.getters.getActiveCell();\n cell.type === CellValueType.empty\n ? this.props.onGridComposerCellFocused()\n : this.props.onComposerContentFocused();\n },\n DELETE: () => {\n this.env.model.dispatch(\"DELETE_CONTENT\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n target: this.env.model.getters.getSelectedZones(),\n });\n },\n BACKSPACE: () => {\n this.env.model.dispatch(\"DELETE_CONTENT\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n target: this.env.model.getters.getSelectedZones(),\n });\n },\n ESCAPE: () => {\n /** TODO: Clean once we introduce proper focus on sub components. Grid should not have to handle all this logic */\n if (this.env.model.getters.hasOpenedPopover()) {\n this.closeOpenedPopover();\n }\n else if (this.menuState.isOpen) {\n this.closeMenu();\n }\n else {\n this.env.model.dispatch(\"CLEAN_CLIPBOARD_HIGHLIGHT\");\n }\n },\n \"CTRL+A\": () => this.env.model.selection.loopSelection(),\n \"CTRL+Z\": () => this.env.model.dispatch(\"REQUEST_UNDO\"),\n \"CTRL+Y\": () => this.env.model.dispatch(\"REQUEST_REDO\"),\n \"CTRL+B\": () => this.env.model.dispatch(\"SET_FORMATTING\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n target: this.env.model.getters.getSelectedZones(),\n style: { bold: !this.env.model.getters.getCurrentStyle().bold },\n }),\n \"CTRL+I\": () => this.env.model.dispatch(\"SET_FORMATTING\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n target: this.env.model.getters.getSelectedZones(),\n style: { italic: !this.env.model.getters.getCurrentStyle().italic },\n }),\n \"CTRL+U\": () => this.env.model.dispatch(\"SET_FORMATTING\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n target: this.env.model.getters.getSelectedZones(),\n style: { underline: !this.env.model.getters.getCurrentStyle().underline },\n }),\n \"ALT+=\": () => {\n var _a;\n const sheetId = this.env.model.getters.getActiveSheetId();\n const mainSelectedZone = this.env.model.getters.getSelectedZone();\n const { anchor } = this.env.model.getters.getSelection();\n const sums = this.env.model.getters.getAutomaticSums(sheetId, mainSelectedZone, anchor.cell);\n if (this.env.model.getters.isSingleCellOrMerge(sheetId, mainSelectedZone) ||\n (this.env.model.getters.isEmpty(sheetId, mainSelectedZone) && sums.length <= 1)) {\n const zone = (_a = sums[0]) === null || _a === void 0 ? void 0 : _a.zone;\n const zoneXc = zone ? this.env.model.getters.zoneToXC(sheetId, sums[0].zone) : \"\";\n const formula = `=SUM(${zoneXc})`;\n this.props.onGridComposerCellFocused(formula, { start: 5, end: 5 + zoneXc.length });\n }\n else {\n this.env.model.dispatch(\"SUM_SELECTION\");\n }\n },\n \"CTRL+HOME\": () => {\n const sheetId = this.env.model.getters.getActiveSheetId();\n const { col, row } = this.env.model.getters.getNextVisibleCellPosition({\n sheetId,\n col: 0,\n row: 0,\n });\n this.env.model.selection.selectCell(col, row);\n },\n \"CTRL+END\": () => {\n const sheetId = this.env.model.getters.getActiveSheetId();\n const col = this.env.model.getters.findVisibleHeader(sheetId, \"COL\", range(0, this.env.model.getters.getNumberCols(sheetId)).reverse());\n const row = this.env.model.getters.findVisibleHeader(sheetId, \"ROW\", range(0, this.env.model.getters.getNumberRows(sheetId)).reverse());\n this.env.model.selection.selectCell(col, row);\n },\n \"SHIFT+ \": () => {\n const sheetId = this.env.model.getters.getActiveSheetId();\n const newZone = {\n ...this.env.model.getters.getSelectedZone(),\n left: 0,\n right: this.env.model.getters.getNumberCols(sheetId) - 1,\n };\n const position = this.env.model.getters.getActivePosition();\n this.env.model.selection.selectZone({ cell: position, zone: newZone });\n },\n \"CTRL+ \": () => {\n const sheetId = this.env.model.getters.getActiveSheetId();\n const newZone = {\n ...this.env.model.getters.getSelectedZone(),\n top: 0,\n bottom: this.env.model.getters.getNumberRows(sheetId) - 1,\n };\n const position = this.env.model.getters.getActivePosition();\n this.env.model.selection.selectZone({ cell: position, zone: newZone });\n },\n \"CTRL+SHIFT+ \": () => {\n this.env.model.selection.selectAll();\n },\n \"SHIFT+PAGEDOWN\": () => {\n this.env.model.dispatch(\"ACTIVATE_NEXT_SHEET\");\n },\n \"SHIFT+PAGEUP\": () => {\n this.env.model.dispatch(\"ACTIVATE_PREVIOUS_SHEET\");\n },\n PAGEDOWN: () => this.env.model.dispatch(\"SHIFT_VIEWPORT_DOWN\"),\n PAGEUP: () => this.env.model.dispatch(\"SHIFT_VIEWPORT_UP\"),\n \"CTRL+K\": () => INSERT_LINK(this.env),\n };\n }\n setup() {\n this.menuState = owl.useState({\n isOpen: false,\n position: null,\n menuItems: [],\n });\n this.gridRef = owl.useRef(\"grid\");\n this.hiddenInput = owl.useRef(\"hiddenInput\");\n this.canvasPosition = useAbsoluteBoundingRect(this.gridRef);\n this.hoveredCell = owl.useState({ col: undefined, row: undefined });\n owl.useChildSubEnv({ getPopoverContainerRect: () => this.getGridRect() });\n owl.useExternalListener(document.body, \"cut\", this.copy.bind(this, true));\n owl.useExternalListener(document.body, \"copy\", this.copy.bind(this, false));\n owl.useExternalListener(document.body, \"paste\", this.paste);\n owl.onMounted(() => this.focus());\n this.props.exposeFocus(() => this.focus());\n useGridDrawing(\"canvas\", this.env.model, () => this.env.model.getters.getSheetViewDimensionWithHeaders());\n owl.useEffect(() => this.focus(), () => [this.env.model.getters.getActiveSheetId()]);\n this.onMouseWheel = useWheelHandler((deltaX, deltaY) => {\n this.moveCanvas(deltaX, deltaY);\n this.hoveredCell.col = undefined;\n this.hoveredCell.row = undefined;\n });\n }\n onCellHovered({ col, row }) {\n this.hoveredCell.col = col;\n this.hoveredCell.row = row;\n }\n get gridOverlayDimensions() {\n return `\n top: ${HEADER_HEIGHT}px;\n left: ${HEADER_WIDTH}px;\n height: calc(100% - ${HEADER_HEIGHT + SCROLLBAR_WIDTH$1}px);\n width: calc(100% - ${HEADER_WIDTH + SCROLLBAR_WIDTH$1}px);\n `;\n }\n onClosePopover() {\n if (this.env.model.getters.hasOpenedPopover()) {\n this.closeOpenedPopover();\n }\n this.focus();\n }\n focus() {\n if (!this.env.model.getters.getSelectedFigureId() &&\n this.env.model.getters.getEditionMode() === \"inactive\") {\n this.hiddenInput.el.focus();\n }\n }\n get gridEl() {\n if (!this.gridRef.el) {\n throw new Error(\"Grid el is not defined.\");\n }\n return this.gridRef.el;\n }\n getAutofillPosition() {\n const zone = this.env.model.getters.getSelectedZone();\n const rect = this.env.model.getters.getVisibleRect(zone);\n return {\n left: rect.x + rect.width - AUTOFILL_EDGE_LENGTH / 2,\n top: rect.y + rect.height - AUTOFILL_EDGE_LENGTH / 2,\n };\n }\n isAutoFillActive() {\n const zone = this.env.model.getters.getSelectedZone();\n const rect = this.env.model.getters.getVisibleRect({\n left: zone.right,\n right: zone.right,\n top: zone.bottom,\n bottom: zone.bottom,\n });\n return !(rect.width === 0 || rect.height === 0);\n }\n onGridResized({ height, width }) {\n this.env.model.dispatch(\"RESIZE_SHEETVIEW\", {\n width: width,\n height: height,\n gridOffsetX: HEADER_WIDTH,\n gridOffsetY: HEADER_HEIGHT,\n });\n }\n moveCanvas(deltaX, deltaY) {\n const { scrollX, scrollY } = this.env.model.getters.getActiveSheetDOMScrollInfo();\n this.env.model.dispatch(\"SET_VIEWPORT_OFFSET\", {\n offsetX: scrollX + deltaX,\n offsetY: scrollY + deltaY,\n });\n }\n getClientPositionKey(client) {\n var _a, _b, _c;\n return `${client.id}-${(_a = client.position) === null || _a === void 0 ? void 0 : _a.sheetId}-${(_b = client.position) === null || _b === void 0 ? void 0 : _b.col}-${(_c = client.position) === null || _c === void 0 ? void 0 : _c.row}`;\n }\n isCellHovered(col, row) {\n return this.hoveredCell.col === col && this.hoveredCell.row === row;\n }\n getGridRect() {\n return { ...this.canvasPosition, ...this.env.model.getters.getSheetViewDimensionWithHeaders() };\n }\n // ---------------------------------------------------------------------------\n // Zone selection with mouse\n // ---------------------------------------------------------------------------\n onCellClicked(col, row, { ctrlKey, shiftKey }) {\n if (ctrlKey) {\n this.env.model.dispatch(\"PREPARE_SELECTION_INPUT_EXPANSION\");\n }\n if (this.env.model.getters.hasOpenedPopover()) {\n this.closeOpenedPopover();\n }\n if (this.env.model.getters.getEditionMode() === \"editing\") {\n this.env.model.dispatch(\"STOP_EDITION\");\n }\n if (shiftKey) {\n this.env.model.selection.setAnchorCorner(col, row);\n }\n else if (ctrlKey) {\n this.env.model.selection.addCellToSelection(col, row);\n }\n else {\n this.env.model.selection.selectCell(col, row);\n }\n let prevCol = col;\n let prevRow = row;\n const onMouseMove = (col, row) => {\n if ((col !== prevCol && col != -1) || (row !== prevRow && row != -1)) {\n prevCol = col === -1 ? prevCol : col;\n prevRow = row === -1 ? prevRow : row;\n this.env.model.selection.setAnchorCorner(prevCol, prevRow);\n }\n };\n const onMouseUp = () => {\n this.env.model.dispatch(\"STOP_SELECTION_INPUT\");\n if (this.env.model.getters.isPaintingFormat()) {\n this.env.model.dispatch(\"PASTE\", {\n target: this.env.model.getters.getSelectedZones(),\n });\n }\n };\n dragAndDropBeyondTheViewport(this.env, onMouseMove, onMouseUp);\n }\n onCellDoubleClicked(col, row) {\n const sheetId = this.env.model.getters.getActiveSheetId();\n ({ col, row } = this.env.model.getters.getMainCellPosition({ sheetId, col, row }));\n const cell = this.env.model.getters.getEvaluatedCell({ sheetId, col, row });\n if (cell.type === CellValueType.empty) {\n this.props.onGridComposerCellFocused();\n }\n else {\n this.props.onComposerContentFocused();\n }\n }\n closeOpenedPopover() {\n this.env.model.dispatch(\"CLOSE_CELL_POPOVER\");\n }\n // ---------------------------------------------------------------------------\n // Keyboard interactions\n // ---------------------------------------------------------------------------\n processArrows(ev) {\n ev.preventDefault();\n ev.stopPropagation();\n if (this.env.model.getters.hasOpenedPopover()) {\n this.closeOpenedPopover();\n }\n updateSelectionWithArrowKeys(ev, this.env.model.selection);\n if (this.env.model.getters.isPaintingFormat()) {\n this.env.model.dispatch(\"PASTE\", {\n target: this.env.model.getters.getSelectedZones(),\n });\n }\n }\n onKeydown(ev) {\n if (ev.key.startsWith(\"Arrow\")) {\n this.processArrows(ev);\n return;\n }\n let keyDownString = \"\";\n if (ev.ctrlKey)\n keyDownString += \"CTRL+\";\n if (ev.metaKey)\n keyDownString += \"CTRL+\";\n if (ev.altKey)\n keyDownString += \"ALT+\";\n if (ev.shiftKey)\n keyDownString += \"SHIFT+\";\n keyDownString += ev.key.toUpperCase();\n let handler = this.keyDownMapping[keyDownString];\n if (handler) {\n ev.preventDefault();\n ev.stopPropagation();\n handler();\n return;\n }\n }\n onInput(ev) {\n // the user meant to paste in the sheet, not open the composer with the pasted content\n if (!ev.isComposing && ev.inputType === \"insertFromPaste\") {\n return;\n }\n if (ev.data) {\n // if the user types a character on the grid, it means he wants to start composing the selected cell with that\n // character\n ev.preventDefault();\n ev.stopPropagation();\n this.props.onGridComposerCellFocused(ev.data);\n }\n }\n // ---------------------------------------------------------------------------\n // Context Menu\n // ---------------------------------------------------------------------------\n onInputContextMenu(ev) {\n ev.preventDefault();\n const lastZone = this.env.model.getters.getSelectedZone();\n const { left: col, top: row } = lastZone;\n let type = \"CELL\";\n this.env.model.dispatch(\"STOP_EDITION\");\n if (this.env.model.getters.getActiveCols().has(col)) {\n type = \"COL\";\n }\n else if (this.env.model.getters.getActiveRows().has(row)) {\n type = \"ROW\";\n }\n const { x, y, width, height } = this.env.model.getters.getVisibleRect(lastZone);\n this.toggleContextMenu(type, x + width, y + height);\n }\n onCellRightClicked(col, row, { x, y }) {\n const zones = this.env.model.getters.getSelectedZones();\n const lastZone = zones[zones.length - 1];\n let type = \"CELL\";\n if (!isInside(col, row, lastZone)) {\n this.env.model.selection.getBackToDefault();\n this.env.model.selection.selectCell(col, row);\n }\n else {\n if (this.env.model.getters.getActiveCols().has(col)) {\n type = \"COL\";\n }\n else if (this.env.model.getters.getActiveRows().has(row)) {\n type = \"ROW\";\n }\n }\n this.toggleContextMenu(type, x, y);\n }\n toggleContextMenu(type, x, y) {\n if (this.env.model.getters.hasOpenedPopover()) {\n this.closeOpenedPopover();\n }\n this.menuState.isOpen = true;\n this.menuState.position = { x, y };\n this.menuState.menuItems = registries$1[type]\n .getAll()\n .filter((item) => !item.isVisible || item.isVisible(this.env));\n }\n copy(cut, ev) {\n if (!this.gridEl.contains(document.activeElement)) {\n return;\n }\n const clipboardData = ev.clipboardData;\n if (!clipboardData) {\n this.displayWarningCopyPasteNotSupported();\n return;\n }\n /* If we are currently editing a cell, let the default behavior */\n if (this.env.model.getters.getEditionMode() !== \"inactive\") {\n return;\n }\n if (cut) {\n interactiveCut(this.env);\n }\n else {\n this.env.model.dispatch(\"COPY\");\n }\n const content = this.env.model.getters.getClipboardContent();\n for (const type in content) {\n clipboardData.setData(type, content[type]);\n }\n ev.preventDefault();\n }\n paste(ev) {\n if (!this.gridEl.contains(document.activeElement)) {\n return;\n }\n const clipboardData = ev.clipboardData;\n if (!clipboardData) {\n this.displayWarningCopyPasteNotSupported();\n return;\n }\n if (clipboardData.types.indexOf(ClipboardMIMEType.PlainText) > -1) {\n const content = clipboardData.getData(ClipboardMIMEType.PlainText);\n const target = this.env.model.getters.getSelectedZones();\n const clipboardString = this.env.model.getters.getClipboardTextContent();\n if (clipboardString === content) {\n // the paste actually comes from o-spreadsheet itself\n interactivePaste(this.env, target);\n }\n else {\n interactivePasteFromOS(this.env, target, content);\n }\n }\n }\n displayWarningCopyPasteNotSupported() {\n this.env.raiseError(_lt(\"Copy/Paste is not supported in this browser.\"));\n }\n closeMenu() {\n this.menuState.isOpen = false;\n this.focus();\n }\n }\n Grid.template = \"o-spreadsheet-Grid\";\n Grid.components = {\n GridComposer,\n GridOverlay,\n GridPopover,\n HeadersOverlay,\n Menu,\n Autofill,\n ClientTag,\n Highlight,\n Popover,\n VerticalScrollBar,\n HorizontalScrollBar,\n FilterIconsOverlay,\n };\n Grid.props = {\n sidePanelIsOpen: Boolean,\n exposeFocus: Function,\n focusComposer: String,\n onComposerContentFocused: Function,\n onGridComposerCellFocused: Function,\n };\n\n /**\n * Represent a raw XML string\n */\n class XMLString {\n /**\n * @param xmlString should be a well formed, properly escaped XML string\n */\n constructor(xmlString) {\n this.xmlString = xmlString;\n }\n toString() {\n return this.xmlString;\n }\n }\n const XLSX_CHART_TYPES = [\n \"areaChart\",\n \"area3DChart\",\n \"lineChart\",\n \"line3DChart\",\n \"stockChart\",\n \"radarChart\",\n \"scatterChart\",\n \"pieChart\",\n \"pie3DChart\",\n \"doughnutChart\",\n \"barChart\",\n \"bar3DChart\",\n \"ofPieChart\",\n \"surfaceChart\",\n \"surface3DChart\",\n \"bubbleChart\",\n ];\n\n /** In XLSX color format (no #) */\n const AUTO_COLOR = \"000000\";\n const XLSX_ICONSET_MAP = {\n arrow: \"3Arrows\",\n smiley: \"3Symbols\",\n dot: \"3TrafficLights1\",\n };\n const NAMESPACE = {\n styleSheet: \"http://schemas.openxmlformats.org/spreadsheetml/2006/main\",\n sst: \"http://schemas.openxmlformats.org/spreadsheetml/2006/main\",\n Relationships: \"http://schemas.openxmlformats.org/package/2006/relationships\",\n Types: \"http://schemas.openxmlformats.org/package/2006/content-types\",\n worksheet: \"http://schemas.openxmlformats.org/spreadsheetml/2006/main\",\n workbook: \"http://schemas.openxmlformats.org/spreadsheetml/2006/main\",\n drawing: \"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing\",\n table: \"http://schemas.openxmlformats.org/spreadsheetml/2006/main\",\n revision: \"http://schemas.microsoft.com/office/spreadsheetml/2014/revision\",\n revision3: \"http://schemas.microsoft.com/office/spreadsheetml/2016/revision3\",\n markupCompatibility: \"http://schemas.openxmlformats.org/markup-compatibility/2006\",\n };\n const DRAWING_NS_A = \"http://schemas.openxmlformats.org/drawingml/2006/main\";\n const DRAWING_NS_C = \"http://schemas.openxmlformats.org/drawingml/2006/chart\";\n const CONTENT_TYPES = {\n workbook: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml\",\n sheet: \"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml\",\n sharedStrings: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml\",\n styles: \"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml\",\n drawing: \"application/vnd.openxmlformats-officedocument.drawing+xml\",\n chart: \"application/vnd.openxmlformats-officedocument.drawingml.chart+xml\",\n themes: \"application/vnd.openxmlformats-officedocument.theme+xml\",\n table: \"application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml\",\n pivot: \"application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml\",\n externalLink: \"application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml\",\n };\n const XLSX_RELATION_TYPE = {\n document: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\",\n sheet: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\",\n sharedStrings: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings\",\n styles: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles\",\n drawing: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing\",\n chart: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart\",\n theme: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme\",\n table: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/table\",\n hyperlink: \"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink\",\n };\n const RELATIONSHIP_NSR = \"http://schemas.openxmlformats.org/officeDocument/2006/relationships\";\n const HEIGHT_FACTOR = 0.75; // 100px => 75 u\n const WIDTH_FACTOR = 0.1317; // 100px => 13.17 u\n /** unit : maximum number of characters a column can hold at the standard font size. What. */\n const EXCEL_DEFAULT_COL_WIDTH = 8.43;\n /** unit : points */\n const EXCEL_DEFAULT_ROW_HEIGHT = 12.75;\n const EXCEL_IMPORT_DEFAULT_NUMBER_OF_COLS = 30;\n const EXCEL_IMPORT_DEFAULT_NUMBER_OF_ROWS = 100;\n const FIRST_NUMFMT_ID = 164;\n const FORCE_DEFAULT_ARGS_FUNCTIONS = {\n FLOOR: [{ type: \"NUMBER\", value: 1 }],\n CEILING: [{ type: \"NUMBER\", value: 1 }],\n ROUND: [{ type: \"NUMBER\", value: 0 }],\n ROUNDUP: [{ type: \"NUMBER\", value: 0 }],\n ROUNDDOWN: [{ type: \"NUMBER\", value: 0 }],\n };\n /**\n * This list contains all \"future\" functions that are not compatible with older versions of Excel\n * For more information, see https://docs.microsoft.com/en-us/openspecs/office_standards/ms-xlsx/5d1b6d44-6fc1-4ecd-8fef-0b27406cc2bf\n */\n const NON_RETROCOMPATIBLE_FUNCTIONS = [\n \"ACOT\",\n \"ACOTH\",\n \"AGGREGATE\",\n \"ARABIC\",\n \"BASE\",\n \"BETA.DIST\",\n \"BETA.INV\",\n \"BINOM.DIST\",\n \"BINOM.DIST.RANGE\",\n \"BINOM.INV\",\n \"BITAND\",\n \"BITLSHIFT\",\n \"BITOR\",\n \"BITRSHIFT\",\n \"BITXOR\",\n \"CEILING.MATH\",\n \"CEILING.PRECISE\",\n \"CHISQ.DIST\",\n \"CHISQ.DIST.RT\",\n \"CHISQ.INV\",\n \"CHISQ.INV.RT\",\n \"CHISQ.TEST\",\n \"COMBINA\",\n \"CONCAT\",\n \"CONFIDENCE.NORM\",\n \"CONFIDENCE.T\",\n \"COT\",\n \"COTH\",\n \"COVARIANCE.P\",\n \"COVARIANCE.S\",\n \"CSC\",\n \"CSCH\",\n \"DAYS\",\n \"DECIMAL\",\n \"ERF.PRECISE\",\n \"ERFC.PRECISE\",\n \"EXPON.DIST\",\n \"F.DIST\",\n \"F.DIST.RT\",\n \"F.INV\",\n \"F.INV.RT\",\n \"F.TEST\",\n \"FILTERXML\",\n \"FLOOR.MATH\",\n \"FLOOR.PRECISE\",\n \"FORECAST.ETS\",\n \"FORECAST.ETS.CONFINT\",\n \"FORECAST.ETS.SEASONALITY\",\n \"FORECAST.ETS.STAT\",\n \"FORECAST.LINEAR\",\n \"FORMULATEXT\",\n \"GAMMA\",\n \"GAMMA.DIST\",\n \"GAMMA.INV\",\n \"GAMMALN.PRECISE\",\n \"GAUSS\",\n \"HYPGEOM.DIST\",\n \"IFNA\",\n \"IFS\",\n \"IMCOSH\",\n \"IMCOT\",\n \"IMCSC\",\n \"IMCSCH\",\n \"IMSEC\",\n \"IMSECH\",\n \"IMSINH\",\n \"IMTAN\",\n \"ISFORMULA\",\n \"ISOWEEKNUM\",\n \"LOGNORM.DIST\",\n \"LOGNORM.INV\",\n \"MAXIFS\",\n \"MINIFS\",\n \"MODE.MULT\",\n \"MODE.SNGL\",\n \"MUNIT\",\n \"NEGBINOM.DIST\",\n \"NORM.DIST\",\n \"NORM.INV\",\n \"NORM.S.DIST\",\n \"NORM.S.INV\",\n \"NUMBERVALUE\",\n \"PDURATION\",\n \"PERCENTILE.EXC\",\n \"PERCENTILE.INC\",\n \"PERCENTRANK.EXC\",\n \"PERCENTRANK.INC\",\n \"PERMUTATIONA\",\n \"PHI\",\n \"POISSON.DIST\",\n \"QUARTILE.EXC\",\n \"QUARTILE.INC\",\n \"QUERYSTRING\",\n \"RANK.AVG\",\n \"RANK.EQ\",\n \"RRI\",\n \"SEC\",\n \"SECH\",\n \"SHEET\",\n \"SHEETS\",\n \"SKEW.P\",\n \"STDEV.P\",\n \"STDEV.S\",\n \"SWITCH\",\n \"T.DIST\",\n \"T.DIST.2T\",\n \"T.DIST.RT\",\n \"T.INV\",\n \"T.INV.2T\",\n \"T.TEST\",\n \"TEXTJOIN\",\n \"UNICHAR\",\n \"UNICODE\",\n \"VAR.P\",\n \"VAR.S\",\n \"WEBSERVICE\",\n \"WEIBULL.DIST\",\n \"XOR\",\n \"Z.TEST\",\n ];\n const CONTENT_TYPES_FILE = \"[Content_Types].xml\";\n\n /**\n * Map of the different types of conversions warnings and their name in error messages\n */\n var WarningTypes;\n (function (WarningTypes) {\n WarningTypes[\"DiagonalBorderNotSupported\"] = \"Diagonal Borders\";\n WarningTypes[\"BorderStyleNotSupported\"] = \"Border style\";\n WarningTypes[\"FillStyleNotSupported\"] = \"Fill Style\";\n WarningTypes[\"FontNotSupported\"] = \"Font\";\n WarningTypes[\"HorizontalAlignmentNotSupported\"] = \"Horizontal Alignment\";\n WarningTypes[\"VerticalAlignmentNotSupported\"] = \"Vertical Alignments\";\n WarningTypes[\"MultipleRulesCfNotSupported\"] = \"Multiple rules conditional formats\";\n WarningTypes[\"CfTypeNotSupported\"] = \"Conditional format type\";\n WarningTypes[\"CfFormatBorderNotSupported\"] = \"Borders in conditional formats\";\n WarningTypes[\"CfFormatAlignmentNotSupported\"] = \"Alignment in conditional formats\";\n WarningTypes[\"CfFormatNumFmtNotSupported\"] = \"Num formats in conditional formats\";\n WarningTypes[\"CfIconSetEmptyIconNotSupported\"] = \"IconSets with empty icons\";\n WarningTypes[\"BadlyFormattedHyperlink\"] = \"Badly formatted hyperlink\";\n WarningTypes[\"NumFmtIdNotSupported\"] = \"Number format\";\n })(WarningTypes || (WarningTypes = {}));\n class XLSXImportWarningManager {\n constructor() {\n this._parsingWarnings = new Set();\n this._conversionWarnings = new Set();\n }\n addParsingWarning(warning) {\n this._parsingWarnings.add(warning);\n }\n addConversionWarning(warning) {\n this._conversionWarnings.add(warning);\n }\n get warnings() {\n return [...this._parsingWarnings, ...this._conversionWarnings];\n }\n /**\n * Add a warning \"... is not supported\" to the manager.\n *\n * @param type the type of the warning to add\n * @param name optional, name of the element that was not supported\n * @param supported optional, list of the supported elements\n */\n generateNotSupportedWarning(type, name, supported) {\n let warning = `${type} ${name ? '\"' + name + '\" is' : \"are\"} not yet supported. `;\n if (supported) {\n warning += `Only ${supported.join(\", \")} are currently supported.`;\n }\n if (!this._conversionWarnings.has(warning)) {\n this._conversionWarnings.add(warning);\n }\n }\n }\n\n const SUPPORTED_BORDER_STYLES = [\"thin\"];\n const SUPPORTED_HORIZONTAL_ALIGNMENTS = [\"general\", \"left\", \"center\", \"right\"];\n const SUPPORTED_FONTS = [\"Arial\"];\n const SUPPORTED_FILL_PATTERNS = [\"solid\"];\n const SUPPORTED_CF_TYPES = [\n \"expression\",\n \"cellIs\",\n \"colorScale\",\n \"iconSet\",\n \"containsText\",\n \"notContainsText\",\n \"beginsWith\",\n \"endsWith\",\n \"containsBlanks\",\n \"notContainsBlanks\",\n ];\n /** Map between cell type in XLSX file and human readable cell type */\n const CELL_TYPE_CONVERSION_MAP = {\n b: \"boolean\",\n d: \"date\",\n e: \"error\",\n inlineStr: \"inlineStr\",\n n: \"number\",\n s: \"sharedString\",\n str: \"str\",\n };\n /** Conversion map Border Style in XLSX <=> Border style in o_spreadsheet*/\n const BORDER_STYLE_CONVERSION_MAP = {\n dashDot: \"thin\",\n dashDotDot: \"thin\",\n dashed: \"thin\",\n dotted: \"thin\",\n double: \"thin\",\n hair: \"thin\",\n medium: \"thin\",\n mediumDashDot: \"thin\",\n mediumDashDotDot: \"thin\",\n mediumDashed: \"thin\",\n none: undefined,\n slantDashDot: \"thin\",\n thick: \"thin\",\n thin: \"thin\",\n };\n /** Conversion map Horizontal Alignment in XLSX <=> Horizontal Alignment in o_spreadsheet*/\n const H_ALIGNMENT_CONVERSION_MAP = {\n general: undefined,\n left: \"left\",\n center: \"center\",\n right: \"right\",\n fill: \"left\",\n justify: \"left\",\n centerContinuous: \"center\",\n distributed: \"center\",\n };\n /** Convert the \"CellIs\" cf operator.\n * We have all the operators that the xlsx have, but ours begin with a uppercase character */\n function convertCFCellIsOperator(xlsxCfOperator) {\n return (xlsxCfOperator.slice(0, 1).toUpperCase() +\n xlsxCfOperator.slice(1));\n }\n /** Conversion map CF types in XLSX <=> Cf types in o_spreadsheet */\n const CF_TYPE_CONVERSION_MAP = {\n aboveAverage: undefined,\n expression: undefined,\n cellIs: undefined,\n colorScale: undefined,\n dataBar: undefined,\n iconSet: undefined,\n top10: undefined,\n uniqueValues: undefined,\n duplicateValues: undefined,\n containsText: \"ContainsText\",\n notContainsText: \"NotContains\",\n beginsWith: \"BeginsWith\",\n endsWith: \"EndsWith\",\n containsBlanks: \"IsEmpty\",\n notContainsBlanks: \"IsNotEmpty\",\n containsErrors: undefined,\n notContainsErrors: undefined,\n timePeriod: undefined,\n };\n /** Conversion map CF thresholds types in XLSX <=> Cf thresholds types in o_spreadsheet */\n const CF_THRESHOLD_CONVERSION_MAP = {\n num: \"number\",\n percent: \"percentage\",\n max: \"value\",\n min: \"value\",\n percentile: \"percentile\",\n formula: \"formula\",\n };\n /**\n * Conversion map between Excels IconSets and our own IconSets. The string is the key of the iconset in the ICON_SETS constant.\n *\n * NoIcons is undefined instead of an empty string because we don't support it and need to mange it separately.\n */\n const ICON_SET_CONVERSION_MAP = {\n NoIcons: undefined,\n \"3Arrows\": \"arrows\",\n \"3ArrowsGray\": \"arrows\",\n \"3Symbols\": \"smiley\",\n \"3Symbols2\": \"smiley\",\n \"3Signs\": \"dots\",\n \"3Flags\": \"dots\",\n \"3TrafficLights1\": \"dots\",\n \"3TrafficLights2\": \"dots\",\n \"4Arrows\": \"arrows\",\n \"4ArrowsGray\": \"arrows\",\n \"4RedToBlack\": \"dots\",\n \"4Rating\": \"smiley\",\n \"4TrafficLights\": \"dots\",\n \"5Arrows\": \"arrows\",\n \"5ArrowsGray\": \"arrows\",\n \"5Rating\": \"smiley\",\n \"5Quarters\": \"dots\",\n \"3Stars\": \"smiley\",\n \"3Triangles\": \"arrows\",\n \"5Boxes\": \"dots\",\n };\n /** Map between legend position in XLSX file and human readable position */\n const DRAWING_LEGEND_POSITION_CONVERSION_MAP = {\n b: \"bottom\",\n t: \"top\",\n l: \"left\",\n r: \"right\",\n tr: \"right\",\n };\n /** Conversion map chart types in XLSX <=> Cf chart types o_spreadsheet (undefined for unsupported chart types)*/\n const CHART_TYPE_CONVERSION_MAP = {\n areaChart: undefined,\n area3DChart: undefined,\n lineChart: \"line\",\n line3DChart: undefined,\n stockChart: undefined,\n radarChart: undefined,\n scatterChart: undefined,\n pieChart: \"pie\",\n pie3DChart: undefined,\n doughnutChart: \"pie\",\n barChart: \"bar\",\n bar3DChart: undefined,\n ofPieChart: undefined,\n surfaceChart: undefined,\n surface3DChart: undefined,\n bubbleChart: undefined,\n };\n /** Conversion map for the SUBTOTAL(index, formula) function in xlsx, index <=> actual function*/\n const SUBTOTAL_FUNCTION_CONVERSION_MAP = {\n \"1\": \"AVERAGE\",\n \"2\": \"COUNT\",\n \"3\": \"COUNTA\",\n \"4\": \"MAX\",\n \"5\": \"MIN\",\n \"6\": \"PRODUCT\",\n \"7\": \"STDEV\",\n \"8\": \"STDEVP\",\n \"9\": \"SUM\",\n \"10\": \"VAR\",\n \"11\": \"VARP\",\n \"101\": \"AVERAGE\",\n \"102\": \"COUNT\",\n \"103\": \"COUNTA\",\n \"104\": \"MAX\",\n \"105\": \"MIN\",\n \"106\": \"PRODUCT\",\n \"107\": \"STDEV\",\n \"108\": \"STDEVP\",\n \"109\": \"SUM\",\n \"110\": \"VAR\",\n \"111\": \"VARP\",\n };\n /** Mapping between Excel format indexes (see XLSX_FORMAT_MAP) and some supported formats */\n const XLSX_FORMATS_CONVERSION_MAP = {\n 0: \"\",\n 1: \"0\",\n 2: \"0.00\",\n 3: \"#,#00\",\n 4: \"#,##0.00\",\n 9: \"0%\",\n 10: \"0.00%\",\n 11: undefined,\n 12: undefined,\n 13: undefined,\n 14: \"m/d/yyyy\",\n 15: \"m/d/yyyy\",\n 16: \"m/d/yyyy\",\n 17: \"m/d/yyyy\",\n 18: \"hh:mm:ss a\",\n 19: \"hh:mm:ss a\",\n 20: \"hhhh:mm:ss\",\n 21: \"hhhh:mm:ss\",\n 22: \"m/d/yy h:mm\",\n 37: undefined,\n 38: undefined,\n 39: undefined,\n 40: undefined,\n 45: \"hhhh:mm:ss\",\n 46: \"hhhh:mm:ss\",\n 47: \"hhhh:mm:ss\",\n 48: undefined,\n 49: undefined,\n };\n /**\n * Mapping format index to format defined by default\n *\n * OpenXML $18.8.30\n * */\n const XLSX_FORMAT_MAP = {\n \"0\": 1,\n \"0.00\": 2,\n \"#,#00\": 3,\n \"#,##0.00\": 4,\n \"0%\": 9,\n \"0.00%\": 10,\n \"0.00E+00\": 11,\n \"# ?/?\": 12,\n \"# ??/??\": 13,\n \"mm-dd-yy\": 14,\n \"d-mm-yy\": 15,\n \"mm-yy\": 16,\n \"mmm-yy\": 17,\n \"h:mm AM/PM\": 18,\n \"h:mm:ss AM/PM\": 19,\n \"h:mm\": 20,\n \"h:mm:ss\": 21,\n \"m/d/yy h:mm\": 22,\n \"#,##0 ;(#,##0)\": 37,\n \"#,##0 ;[Red](#,##0)\": 38,\n \"#,##0.00;(#,##0.00)\": 39,\n \"#,##0.00;[Red](#,##0.00)\": 40,\n \"mm:ss\": 45,\n \"[h]:mm:ss\": 46,\n \"mmss.0\": 47,\n \"##0.0E+0\": 48,\n \"@\": 49,\n \"hh:mm:ss a\": 19, // TODO: discuss: this format is not recognized by excel for example (doesn't follow their guidelines I guess)\n };\n /** OpenXML $18.8.27 */\n const XLSX_INDEXED_COLORS = {\n 0: \"000000\",\n 1: \"FFFFFF\",\n 2: \"FF0000\",\n 3: \"00FF00\",\n 4: \"0000FF\",\n 5: \"FFFF00\",\n 6: \"FF00FF\",\n 7: \"00FFFF\",\n 8: \"000000\",\n 9: \"FFFFFF\",\n 10: \"FF0000\",\n 11: \"00FF00\",\n 12: \"0000FF\",\n 13: \"FFFF00\",\n 14: \"FF00FF\",\n 15: \"00FFFF\",\n 16: \"800000\",\n 17: \"008000\",\n 18: \"000080\",\n 19: \"808000\",\n 20: \"800080\",\n 21: \"008080\",\n 22: \"C0C0C0\",\n 23: \"808080\",\n 24: \"9999FF\",\n 25: \"993366\",\n 26: \"FFFFCC\",\n 27: \"CCFFFF\",\n 28: \"660066\",\n 29: \"FF8080\",\n 30: \"0066CC\",\n 31: \"CCCCFF\",\n 32: \"000080\",\n 33: \"FF00FF\",\n 34: \"FFFF00\",\n 35: \"00FFFF\",\n 36: \"800080\",\n 37: \"800000\",\n 38: \"008080\",\n 39: \"0000FF\",\n 40: \"00CCFF\",\n 41: \"CCFFFF\",\n 42: \"CCFFCC\",\n 43: \"FFFF99\",\n 44: \"99CCFF\",\n 45: \"FF99CC\",\n 46: \"CC99FF\",\n 47: \"FFCC99\",\n 48: \"3366FF\",\n 49: \"33CCCC\",\n 50: \"99CC00\",\n 51: \"FFCC00\",\n 52: \"FF9900\",\n 53: \"FF6600\",\n 54: \"666699\",\n 55: \"969696\",\n 56: \"003366\",\n 57: \"339966\",\n 58: \"003300\",\n 59: \"333300\",\n 60: \"993300\",\n 61: \"993366\",\n 62: \"333399\",\n 63: \"333333\",\n 64: \"000000\",\n 65: \"FFFFFF\", // system background\n };\n\n /**\n * Most of the functions could stay private, but are exported for testing purposes\n */\n /**\n *\n * Extract the color referenced inside of an XML element and return it as an hex string #RRGGBBAA (or #RRGGBB\n * if alpha = FF)\n *\n * The color is an attribute of the element that can be :\n * - rgb : an rgb string\n * - theme : a reference to a theme element\n * - auto : automatic coloring. Return const AUTO_COLOR in constants.ts.\n * - indexed : a legacy indexing scheme for colors. The only value that should be present in a xlsx is\n * 64 = System Foreground, that we can replace with AUTO_COLOR.\n */\n function convertColor(xlsxColor) {\n if (!xlsxColor) {\n return undefined;\n }\n let rgb;\n if (xlsxColor.rgb) {\n rgb = xlsxColor.rgb;\n }\n else if (xlsxColor.auto) {\n rgb = AUTO_COLOR;\n }\n else if (xlsxColor.indexed) {\n rgb = XLSX_INDEXED_COLORS[xlsxColor.indexed];\n }\n else {\n return undefined;\n }\n rgb = xlsxColorToHEXA(rgb);\n if (xlsxColor.tint) {\n rgb = applyTint(rgb, xlsxColor.tint);\n }\n rgb = rgb.toUpperCase();\n // Remove unnecessary alpha\n if (rgb.length === 9 && rgb.endsWith(\"FF\")) {\n rgb = rgb.slice(0, 7);\n }\n return rgb;\n }\n /**\n * Convert a hex color AARRGGBB (or RRGGBB)(representation inside XLSX Xmls) to a standard js color\n * representation #RRGGBBAA\n */\n function xlsxColorToHEXA(color) {\n if (color.length === 6)\n return \"#\" + color + \"FF\";\n return \"#\" + color.slice(2) + color.slice(0, 2);\n }\n /**\n * Apply tint to a color (see OpenXml spec \u00a718.3.1.15);\n */\n function applyTint(color, tint) {\n const rgba = colorToRGBA(color);\n const hsla = rgbaToHSLA(rgba);\n if (tint < 0) {\n hsla.l = hsla.l * (1 + tint);\n }\n if (tint > 0) {\n hsla.l = hsla.l * (1 - tint) + (100 - 100 * (1 - tint));\n }\n return rgbaToHex(hslaToRGBA(hsla));\n }\n /**\n * Convert a hex + alpha color string to an integer representation. Also remove the alpha.\n *\n * eg. #FF0000FF => 4278190335\n */\n function hexaToInt(hex) {\n if (hex.length === 9) {\n hex = hex.slice(0, 7);\n }\n return parseInt(hex.replace(\"#\", \"\"), 16);\n }\n\n /**\n * Get the relative path between two files\n *\n * Eg.:\n * from \"folder1/file1.txt\" to \"folder2/file2.txt\" => \"../folder2/file2.txt\"\n */\n function getRelativePath(from, to) {\n const fromPathParts = from.split(\"/\");\n const toPathParts = to.split(\"/\");\n let relPath = \"\";\n let startIndex = 0;\n for (let i = 0; i < fromPathParts.length - 1; i++) {\n if (fromPathParts[i] === toPathParts[i]) {\n startIndex++;\n }\n else {\n relPath += \"../\";\n }\n }\n relPath += toPathParts.slice(startIndex).join(\"/\");\n return relPath;\n }\n /**\n * Convert an array of element into an object where the objects keys were the elements position in the array.\n * Can give an offset as argument, and all the array indexes will we shifted by this offset in the returned object.\n *\n * eg. : [\"a\", \"b\"] => {0:\"a\", 1:\"b\"}\n */\n function arrayToObject(array, indexOffset = 0) {\n const obj = {};\n for (let i = 0; i < array.length; i++) {\n if (array[i]) {\n obj[i + indexOffset] = array[i];\n }\n }\n return obj;\n }\n /**\n * Convert an object whose keys are numbers to an array were the element index was their key in the object.\n *\n * eg. : {0:\"a\", 2:\"b\"} => [\"a\", undefined, \"b\"]\n */\n function objectToArray(obj) {\n const arr = [];\n for (let key of Object.keys(obj).map(Number)) {\n arr[key] = obj[key];\n }\n return arr;\n }\n /**\n * In xlsx we can have string with unicode characters with the format _x00fa_.\n * Replace with characters understandable by JS\n */\n function fixXlsxUnicode(str) {\n return str.replace(/_x([0-9a-zA-Z]{4})_/g, (match, code) => {\n return String.fromCharCode(parseInt(code, 16));\n });\n }\n\n function convertBorders(data, warningManager) {\n const borderArray = data.borders.map((border) => {\n addBorderWarnings(border, warningManager);\n const b = {\n top: convertBorderDescr$1(border.top, warningManager),\n bottom: convertBorderDescr$1(border.bottom, warningManager),\n left: convertBorderDescr$1(border.left, warningManager),\n right: convertBorderDescr$1(border.right, warningManager),\n };\n Object.keys(b).forEach((key) => b[key] === undefined && delete b[key]);\n return b;\n });\n return arrayToObject(borderArray, 1);\n }\n function convertBorderDescr$1(borderDescr, warningManager) {\n if (!borderDescr)\n return undefined;\n addBorderDescrWarnings(borderDescr, warningManager);\n const style = BORDER_STYLE_CONVERSION_MAP[borderDescr.style];\n return style ? [style, convertColor(borderDescr.color)] : undefined;\n }\n function convertStyles(data, warningManager) {\n const stylesArray = data.styles.map((style) => {\n return convertStyle({\n fontStyle: data.fonts[style.fontId],\n fillStyle: data.fills[style.fillId],\n alignment: style.alignment,\n }, warningManager);\n });\n return arrayToObject(stylesArray, 1);\n }\n function convertStyle(styleStruct, warningManager) {\n var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;\n addStyleWarnings(styleStruct === null || styleStruct === void 0 ? void 0 : styleStruct.fontStyle, styleStruct === null || styleStruct === void 0 ? void 0 : styleStruct.fillStyle, warningManager);\n addHorizontalAlignmentWarnings((_a = styleStruct === null || styleStruct === void 0 ? void 0 : styleStruct.alignment) === null || _a === void 0 ? void 0 : _a.horizontal, warningManager);\n addVerticalAlignmentWarnings((_b = styleStruct === null || styleStruct === void 0 ? void 0 : styleStruct.alignment) === null || _b === void 0 ? void 0 : _b.vertical, warningManager);\n return {\n bold: (_c = styleStruct.fontStyle) === null || _c === void 0 ? void 0 : _c.bold,\n italic: (_d = styleStruct.fontStyle) === null || _d === void 0 ? void 0 : _d.italic,\n strikethrough: (_e = styleStruct.fontStyle) === null || _e === void 0 ? void 0 : _e.strike,\n underline: (_f = styleStruct.fontStyle) === null || _f === void 0 ? void 0 : _f.underline,\n align: ((_g = styleStruct.alignment) === null || _g === void 0 ? void 0 : _g.horizontal)\n ? H_ALIGNMENT_CONVERSION_MAP[styleStruct.alignment.horizontal]\n : undefined,\n // In xlsx fills, bgColor is the color of the fill, and fgColor is the color of the pattern above the background, except in solid fills\n fillColor: ((_h = styleStruct.fillStyle) === null || _h === void 0 ? void 0 : _h.patternType) === \"solid\"\n ? convertColor((_j = styleStruct.fillStyle) === null || _j === void 0 ? void 0 : _j.fgColor)\n : convertColor((_k = styleStruct.fillStyle) === null || _k === void 0 ? void 0 : _k.bgColor),\n textColor: convertColor((_l = styleStruct.fontStyle) === null || _l === void 0 ? void 0 : _l.color),\n fontSize: ((_m = styleStruct.fontStyle) === null || _m === void 0 ? void 0 : _m.size)\n ? getClosestFontSize(styleStruct.fontStyle.size)\n : undefined,\n };\n }\n function convertFormats(data, warningManager) {\n const formats = [];\n for (let style of data.styles) {\n const format = convertXlsxFormat(style.numFmtId, data.numFmts, warningManager);\n if (format) {\n formats[style.numFmtId] = format;\n }\n }\n return arrayToObject(formats, 1);\n }\n /**\n * Convert excel format to o_spreadsheet format\n *\n * Excel format are defined in openXML \u00a718.8.31\n */\n function convertXlsxFormat(numFmtId, formats, warningManager) {\n var _a, _b, _c;\n if (numFmtId === 0) {\n return undefined;\n }\n // Format is either defined in the imported data, or the formatId is defined in openXML \u00a718.8.30\n let format = XLSX_FORMATS_CONVERSION_MAP[numFmtId] || ((_a = formats.find((f) => f.id === numFmtId)) === null || _a === void 0 ? void 0 : _a.format);\n if (format) {\n try {\n let convertedFormat = format.replace(/(.*?);.*/, \"$1\"); // only take first part of multi-part format\n convertedFormat = convertedFormat.replace(/\\[(.*)-[A-Z0-9]{3}\\]/g, \"[$1]\"); // remove currency and locale/date system/number system info (ECMA \u00a718.8.31)\n convertedFormat = convertedFormat.replace(/\\[\\$\\]/g, \"\"); // remove empty bocks\n // Quotes in format escape sequences of characters. ATM we only support [$...] blocks to escape characters, and only one of them per format\n const numberOfQuotes = ((_b = convertedFormat.match(/\"/g)) === null || _b === void 0 ? void 0 : _b.length) || 0;\n const numberOfOpenBrackets = ((_c = convertedFormat.match(/\\[/g)) === null || _c === void 0 ? void 0 : _c.length) || 0;\n if (numberOfQuotes / 2 + numberOfOpenBrackets > 1) {\n throw new Error(\"Multiple escaped blocks in format\");\n }\n convertedFormat = convertedFormat.replace(/\"(.*)\"/g, \"[$$$1]\"); // replace '\"...\"' by '[$...]'\n convertedFormat = convertedFormat.replace(/_.{1}/g, \"\"); // _ == ignore with of next char for align purposes. Not supported ATM\n convertedFormat = convertedFormat.replace(/\\*.{1}/g, \"\"); // * == repeat next character enough to fill the line. Not supported ATM\n convertedFormat = convertedFormat.replace(/\\\\ /g, \" \"); // unescape spaces\n formatValue(0, convertedFormat);\n return convertedFormat;\n }\n catch (e) { }\n }\n warningManager.generateNotSupportedWarning(WarningTypes.NumFmtIdNotSupported, format || `nmFmtId ${numFmtId}`);\n return undefined;\n }\n /**\n * We currently only support only a set of font sizes, we cannot define new font sizes.\n * This function adapts an arbitrary font size to the closest supported font size.\n */\n function getClosestFontSize(fontSize) {\n const supportedSizes = Object.keys(fontSizeMap).map(Number);\n const closest = supportedSizes.reduce((prev, curr) => Math.abs(curr - fontSize) < Math.abs(prev - fontSize) ? curr : prev);\n return closest;\n }\n // ---------------------------------------------------------------------------\n // Warnings\n // ---------------------------------------------------------------------------\n function addStyleWarnings(font, fill, warningManager) {\n if (font && font.name && !SUPPORTED_FONTS.includes(font.name)) {\n warningManager.generateNotSupportedWarning(WarningTypes.FontNotSupported, font.name, SUPPORTED_FONTS);\n }\n if (fill && fill.patternType && !SUPPORTED_FILL_PATTERNS.includes(fill.patternType)) {\n warningManager.generateNotSupportedWarning(WarningTypes.FillStyleNotSupported, fill.patternType, SUPPORTED_FILL_PATTERNS);\n }\n }\n function addBorderDescrWarnings(borderDescr, warningManager) {\n if (!SUPPORTED_BORDER_STYLES.includes(borderDescr.style)) {\n warningManager.generateNotSupportedWarning(WarningTypes.BorderStyleNotSupported, borderDescr.style, SUPPORTED_BORDER_STYLES);\n }\n }\n function addBorderWarnings(border, warningManager) {\n if (border.diagonal) {\n warningManager.generateNotSupportedWarning(WarningTypes.DiagonalBorderNotSupported);\n }\n }\n function addHorizontalAlignmentWarnings(alignment, warningManager) {\n if (alignment && !SUPPORTED_HORIZONTAL_ALIGNMENTS.includes(alignment)) {\n warningManager.generateNotSupportedWarning(WarningTypes.HorizontalAlignmentNotSupported, alignment, SUPPORTED_HORIZONTAL_ALIGNMENTS);\n }\n }\n function addVerticalAlignmentWarnings(alignment, warningManager) {\n if (alignment) {\n warningManager.generateNotSupportedWarning(WarningTypes.VerticalAlignmentNotSupported);\n }\n }\n\n function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {\n const cfs = [];\n let cfId = 1;\n for (let cf of xlsxCfs) {\n if (cf.cfRules.length === 0)\n continue;\n addCfConversionWarnings(cf, dxfs, warningManager);\n const rule = cf.cfRules[0];\n let operator;\n const values = [];\n if (rule.dxfId === undefined && !(rule.type === \"colorScale\" || rule.type === \"iconSet\"))\n continue;\n switch (rule.type) {\n case \"aboveAverage\":\n case \"containsErrors\":\n case \"notContainsErrors\":\n case \"dataBar\":\n case \"duplicateValues\":\n case \"expression\":\n case \"top10\":\n case \"uniqueValues\":\n case \"timePeriod\":\n // Not supported\n continue;\n case \"colorScale\":\n const colorScale = convertColorScale(cfId++, cf);\n if (colorScale) {\n cfs.push(colorScale);\n }\n continue;\n case \"iconSet\":\n const iconSet = convertIconSet(cfId++, cf, warningManager);\n if (iconSet) {\n cfs.push(iconSet);\n }\n continue;\n case \"containsText\":\n case \"notContainsText\":\n case \"beginsWith\":\n case \"endsWith\":\n if (!rule.text)\n continue;\n operator = CF_TYPE_CONVERSION_MAP[rule.type];\n values.push(rule.text);\n break;\n case \"containsBlanks\":\n case \"notContainsBlanks\":\n operator = CF_TYPE_CONVERSION_MAP[rule.type];\n break;\n case \"cellIs\":\n if (!rule.operator || !rule.formula || rule.formula.length === 0)\n continue;\n operator = convertCFCellIsOperator(rule.operator);\n values.push(rule.formula[0]);\n if (rule.formula.length === 2) {\n values.push(rule.formula[1]);\n }\n break;\n }\n if (operator && rule.dxfId !== undefined) {\n cfs.push({\n id: (cfId++).toString(),\n ranges: cf.sqref,\n stopIfTrue: rule.stopIfTrue,\n rule: {\n type: \"CellIsRule\",\n operator: operator,\n values: values,\n style: convertStyle({ fontStyle: dxfs[rule.dxfId].font, fillStyle: dxfs[rule.dxfId].fill }, warningManager),\n },\n });\n }\n }\n return cfs;\n }\n function convertColorScale(id, xlsxCf) {\n const scale = xlsxCf.cfRules[0].colorScale;\n if (!scale ||\n scale.cfvos.length !== scale.colors.length ||\n scale.cfvos.length < 2 ||\n scale.cfvos.length > 3) {\n return undefined;\n }\n const thresholds = [];\n for (let i = 0; i < scale.cfvos.length; i++) {\n thresholds.push({\n color: hexaToInt(convertColor(scale.colors[i]) || \"#FFFFFF\"),\n type: CF_THRESHOLD_CONVERSION_MAP[scale.cfvos[i].type],\n value: scale.cfvos[i].value,\n });\n }\n const minimum = thresholds[0];\n const maximum = thresholds.length === 2 ? thresholds[1] : thresholds[2];\n const midpoint = thresholds.length === 3 ? thresholds[1] : undefined;\n return {\n id: id.toString(),\n stopIfTrue: xlsxCf.cfRules[0].stopIfTrue,\n ranges: xlsxCf.sqref,\n rule: { type: \"ColorScaleRule\", minimum, midpoint, maximum },\n };\n }\n /**\n * Convert Icons Sets.\n *\n * In the Xlsx extension of OpenXml, the IconSets can either be simply an IconSet, or a list of Icons\n * (ie. their respective IconSet and their id in this set).\n *\n * In the case of a list of icons :\n * - The order of the icons is lower => middle => upper\n * - The their ids are : 0 : bad, 1 : neutral, 2 : good\n */\n function convertIconSet(id, xlsxCf, warningManager) {\n const xlsxIconSet = xlsxCf.cfRules[0].iconSet;\n if (!xlsxIconSet)\n return undefined;\n let cfVos = xlsxIconSet.cfvos;\n let cfIcons = xlsxIconSet.cfIcons;\n if (cfVos.length < 3 || (cfIcons && cfIcons.length < 3)) {\n return undefined;\n }\n // We don't support icon sets with more than 3 icons, so take the extrema and the middle.\n if (cfVos.length > 3) {\n cfVos = [cfVos[0], cfVos[Math.floor(cfVos.length / 2)], cfVos[cfVos.length - 1]];\n }\n if (cfIcons && cfIcons.length > 3) {\n cfIcons = [cfIcons[0], cfIcons[Math.floor(cfIcons.length / 2)], cfIcons[cfIcons.length - 1]];\n }\n // In xlsx, the thresholds are NOT in the first cfVo, but on the second and third\n const thresholds = [];\n for (let i = 1; i <= 2; i++) {\n const type = CF_THRESHOLD_CONVERSION_MAP[cfVos[i].type];\n if (type === \"value\") {\n return undefined;\n }\n thresholds.push({\n value: cfVos[i].value || \"\",\n operator: cfVos[i].gte ? \"ge\" : \"gt\",\n type: type,\n });\n }\n let icons = {\n lower: cfIcons\n ? convertIcons(cfIcons[0].iconSet, cfIcons[0].iconId)\n : convertIcons(xlsxIconSet.iconSet, 0),\n middle: cfIcons\n ? convertIcons(cfIcons[1].iconSet, cfIcons[1].iconId)\n : convertIcons(xlsxIconSet.iconSet, 1),\n upper: cfIcons\n ? convertIcons(cfIcons[2].iconSet, cfIcons[2].iconId)\n : convertIcons(xlsxIconSet.iconSet, 2),\n };\n if (xlsxIconSet.reverse) {\n icons = { upper: icons.lower, middle: icons.middle, lower: icons.upper };\n }\n // We don't support empty icons in an IconSet, put a dot icon instead\n for (let key of Object.keys(icons)) {\n if (!icons[key]) {\n warningManager.generateNotSupportedWarning(WarningTypes.CfIconSetEmptyIconNotSupported);\n switch (key) {\n case \"upper\":\n icons[key] = ICON_SETS.dots.good;\n break;\n case \"middle\":\n icons[key] = ICON_SETS.dots.neutral;\n break;\n case \"lower\":\n icons[key] = ICON_SETS.dots.bad;\n break;\n }\n }\n }\n return {\n id: id.toString(),\n stopIfTrue: xlsxCf.cfRules[0].stopIfTrue,\n ranges: xlsxCf.sqref,\n rule: {\n type: \"IconSetRule\",\n icons: icons,\n upperInflectionPoint: thresholds[1],\n lowerInflectionPoint: thresholds[0],\n },\n };\n }\n /**\n * Convert an icon from a XLSX.\n *\n * The indexes are : 0 : bad, 1 : neutral, 2 : good\n */\n function convertIcons(xlsxIconSet, index) {\n const iconSet = ICON_SET_CONVERSION_MAP[xlsxIconSet];\n if (!iconSet)\n return \"\";\n return index === 0\n ? ICON_SETS[iconSet].bad\n : index === 1\n ? ICON_SETS[iconSet].neutral\n : ICON_SETS[iconSet].good;\n }\n // ---------------------------------------------------------------------------\n // Warnings\n // ---------------------------------------------------------------------------\n function addCfConversionWarnings(cf, dxfs, warningManager) {\n if (cf.cfRules.length > 1) {\n warningManager.generateNotSupportedWarning(WarningTypes.MultipleRulesCfNotSupported);\n }\n if (!SUPPORTED_CF_TYPES.includes(cf.cfRules[0].type)) {\n warningManager.generateNotSupportedWarning(WarningTypes.CfTypeNotSupported, cf.cfRules[0].type);\n }\n if (cf.cfRules[0].dxfId) {\n const dxf = dxfs[cf.cfRules[0].dxfId];\n if (dxf.border) {\n warningManager.generateNotSupportedWarning(WarningTypes.CfFormatBorderNotSupported);\n }\n if (dxf.alignment) {\n warningManager.generateNotSupportedWarning(WarningTypes.CfFormatAlignmentNotSupported);\n }\n if (dxf.numFmt) {\n warningManager.generateNotSupportedWarning(WarningTypes.CfFormatNumFmtNotSupported);\n }\n }\n }\n\n // -------------------------------------\n // CF HELPERS\n // -------------------------------------\n /**\n * Convert the conditional formatting o-spreadsheet operator to\n * the corresponding excel operator.\n * */\n function convertOperator(operator) {\n switch (operator) {\n case \"IsNotEmpty\":\n return \"notContainsBlanks\";\n case \"IsEmpty\":\n return \"containsBlanks\";\n case \"NotContains\":\n return \"notContainsBlanks\";\n default:\n return operator.charAt(0).toLowerCase() + operator.slice(1);\n }\n }\n // -------------------------------------\n // WORKSHEET HELPERS\n // -------------------------------------\n function getCellType(value) {\n switch (typeof value) {\n case \"boolean\":\n return \"b\";\n case \"string\":\n return \"str\";\n case \"number\":\n return \"n\";\n }\n }\n /**\n * For some reason, Excel will only take the devicePixelRatio (i.e. interface scale on Windows desktop)\n * into account for the height.\n */\n function convertHeightToExcel(height) {\n return Math.round(HEIGHT_FACTOR * height * window.devicePixelRatio * 100) / 100;\n }\n function convertWidthToExcel(width) {\n return Math.round(WIDTH_FACTOR * width * 100) / 100;\n }\n function convertHeightFromExcel(height) {\n if (!height)\n return height;\n return Math.round((height / HEIGHT_FACTOR) * 100) / 100;\n }\n function convertWidthFromExcel(width) {\n if (!width)\n return width;\n return Math.round((width / WIDTH_FACTOR) * 100) / 100;\n }\n function convertBorderDescr(descr) {\n if (!descr) {\n return undefined;\n }\n return {\n style: descr[0],\n color: { rgb: descr[1] },\n };\n }\n function extractStyle(cell, data) {\n let style = {};\n if (cell.style) {\n style = data.styles[cell.style];\n }\n const format = cell.format ? data.formats[cell.format] : undefined;\n const exportedBorder = {};\n if (cell.border) {\n const border = data.borders[cell.border];\n exportedBorder.left = convertBorderDescr(border.left);\n exportedBorder.right = convertBorderDescr(border.right);\n exportedBorder.bottom = convertBorderDescr(border.bottom);\n exportedBorder.top = convertBorderDescr(border.top);\n }\n const styles = {\n font: {\n size: (style === null || style === void 0 ? void 0 : style.fontSize) || DEFAULT_FONT_SIZE,\n color: { rgb: (style === null || style === void 0 ? void 0 : style.textColor) ? style.textColor : \"000000\" },\n family: 2,\n name: \"Arial\",\n },\n fill: (style === null || style === void 0 ? void 0 : style.fillColor)\n ? {\n fgColor: { rgb: style.fillColor },\n }\n : { reservedAttribute: \"none\" },\n numFmt: format ? { format: format, id: 0 /* id not used for export */ } : undefined,\n border: exportedBorder || {},\n alignment: {\n vertical: \"center\",\n horizontal: style.align,\n },\n };\n styles.font[\"strike\"] = !!(style === null || style === void 0 ? void 0 : style.strikethrough) || undefined;\n styles.font[\"underline\"] = !!(style === null || style === void 0 ? void 0 : style.underline) || undefined;\n styles.font[\"bold\"] = !!(style === null || style === void 0 ? void 0 : style.bold) || undefined;\n styles.font[\"italic\"] = !!(style === null || style === void 0 ? void 0 : style.italic) || undefined;\n return styles;\n }\n function normalizeStyle(construct, styles) {\n const { id: fontId } = pushElement(styles[\"font\"], construct.fonts);\n const { id: fillId } = pushElement(styles[\"fill\"], construct.fills);\n const { id: borderId } = pushElement(styles[\"border\"], construct.borders);\n // Normalize this\n const numFmtId = convertFormat(styles[\"numFmt\"], construct.numFmts);\n const style = {\n fontId,\n fillId,\n borderId,\n numFmtId,\n alignment: {\n vertical: styles.alignment.vertical,\n horizontal: styles.alignment.horizontal,\n },\n };\n const { id } = pushElement(style, construct.styles);\n return id;\n }\n function convertFormat(format, numFmtStructure) {\n if (!format) {\n return 0;\n }\n let formatId = XLSX_FORMAT_MAP[format.format];\n if (!formatId) {\n const { id } = pushElement(format, numFmtStructure);\n formatId = id + FIRST_NUMFMT_ID;\n }\n return formatId;\n }\n /**\n * Add a relation to the given file and return its id.\n */\n function addRelsToFile(relsFiles, path, rel) {\n let relsFile = relsFiles.find((file) => file.path === path);\n // the id is a one-based int casted as string\n let id;\n if (!relsFile) {\n id = \"rId1\";\n relsFiles.push({ path, rels: [{ ...rel, id }] });\n }\n else {\n id = `rId${(relsFile.rels.length + 1).toString()}`;\n relsFile.rels.push({\n ...rel,\n id,\n });\n }\n return id;\n }\n function pushElement(property, propertyList) {\n for (let [key, value] of Object.entries(propertyList)) {\n if (JSON.stringify(value) === JSON.stringify(property)) {\n return { id: parseInt(key, 10), list: propertyList };\n }\n }\n let elemId = propertyList.findIndex((elem) => JSON.stringify(elem) === JSON.stringify(property));\n if (elemId === -1) {\n propertyList.push(property);\n elemId = propertyList.length - 1;\n }\n return {\n id: elemId,\n list: propertyList,\n };\n }\n const chartIds = [];\n /**\n * Convert a chart o-spreadsheet id to a xlsx id which\n * are unsigned integers (starting from 1).\n */\n function convertChartId(chartId) {\n const xlsxId = chartIds.findIndex((id) => id === chartId);\n if (xlsxId === -1) {\n chartIds.push(chartId);\n return chartIds.length;\n }\n return xlsxId + 1;\n }\n /**\n * Convert a value expressed in dot to EMU.\n * EMU = English Metrical Unit\n * There are 914400 EMU per inch.\n *\n * /!\\ A value expressed in EMU cannot be fractional.\n * See https://docs.microsoft.com/en-us/windows/win32/vml/msdn-online-vml-units#other-units-of-measurement\n */\n function convertDotValueToEMU(value) {\n const DPI = 96;\n return Math.round((value * 914400) / DPI);\n }\n function getRangeSize(reference, defaultSheetIndex, data) {\n let xc = reference;\n let sheetName = undefined;\n ({ xc, sheetName } = splitReference(reference));\n let rangeSheetIndex;\n if (sheetName) {\n const index = data.sheets.findIndex((sheet) => sheet.name === sheetName);\n if (index < 0) {\n throw new Error(\"Unable to find a sheet with the name \" + sheetName);\n }\n rangeSheetIndex = index;\n }\n else {\n rangeSheetIndex = Number(defaultSheetIndex);\n }\n const zone = toUnboundedZone(xc);\n if (zone.right === undefined) {\n zone.right = data.sheets[rangeSheetIndex].colNumber;\n }\n if (zone.bottom === undefined) {\n zone.bottom = data.sheets[rangeSheetIndex].rowNumber;\n }\n return (zone.right - zone.left + 1) * (zone.bottom - zone.top + 1);\n }\n function convertEMUToDotValue(value) {\n const DPI = 96;\n return Math.round((value * DPI) / 914400);\n }\n /**\n * Get the position of the start of a column in Excel (in px).\n */\n function getColPosition(colIndex, sheetData) {\n var _a;\n let position = 0;\n for (let i = 0; i < colIndex; i++) {\n const colAtIndex = sheetData.cols.find((col) => i >= col.min && i <= col.max);\n if (colAtIndex === null || colAtIndex === void 0 ? void 0 : colAtIndex.width) {\n position += colAtIndex.width;\n }\n else if ((_a = sheetData.sheetFormat) === null || _a === void 0 ? void 0 : _a.defaultColWidth) {\n position += sheetData.sheetFormat.defaultColWidth;\n }\n else {\n position += EXCEL_DEFAULT_COL_WIDTH;\n }\n }\n return position / WIDTH_FACTOR;\n }\n /**\n * Get the position of the start of a row in Excel (in px).\n */\n function getRowPosition(rowIndex, sheetData) {\n var _a;\n let position = 0;\n for (let i = 0; i < rowIndex; i++) {\n const rowAtIndex = sheetData.rows[i];\n if (rowAtIndex === null || rowAtIndex === void 0 ? void 0 : rowAtIndex.height) {\n position += rowAtIndex.height;\n }\n else if ((_a = sheetData.sheetFormat) === null || _a === void 0 ? void 0 : _a.defaultRowHeight) {\n position += sheetData.sheetFormat.defaultRowHeight;\n }\n else {\n position += EXCEL_DEFAULT_ROW_HEIGHT;\n }\n }\n return position / HEIGHT_FACTOR;\n }\n\n function convertFigures(sheetData) {\n let id = 1;\n return sheetData.figures\n .map((figure) => convertFigure(figure, (id++).toString(), sheetData))\n .filter(isDefined$1);\n }\n function convertFigure(figure, id, sheetData) {\n const x1 = getColPosition(figure.anchors[0].col, sheetData) +\n convertEMUToDotValue(figure.anchors[0].colOffset);\n const x2 = getColPosition(figure.anchors[1].col, sheetData) +\n convertEMUToDotValue(figure.anchors[1].colOffset);\n const y1 = getRowPosition(figure.anchors[0].row, sheetData) +\n convertEMUToDotValue(figure.anchors[0].rowOffset);\n const y2 = getRowPosition(figure.anchors[1].row, sheetData) +\n convertEMUToDotValue(figure.anchors[1].rowOffset);\n const width = x2 - x1;\n const height = y2 - y1;\n const chartData = convertChartData(figure.data);\n if (!chartData)\n return undefined;\n return {\n id: id,\n x: x1,\n y: y1,\n width: width,\n height: height,\n tag: \"chart\",\n data: convertChartData(figure.data),\n };\n }\n function convertChartData(chartData) {\n var _a;\n const labelRange = (_a = chartData.dataSets[0].label) === null || _a === void 0 ? void 0 : _a.replace(/\\$/g, \"\");\n let dataSets = chartData.dataSets.map((data) => data.range.replace(/\\$/g, \"\"));\n // For doughnut charts, in chartJS first dataset = outer dataset, in excel first dataset = inner dataset\n if (chartData.type === \"pie\") {\n dataSets.reverse();\n }\n return {\n dataSets,\n dataSetsHaveTitle: false,\n labelRange,\n title: chartData.title || \"\",\n type: chartData.type,\n background: convertColor({ rgb: chartData.backgroundColor }) || \"#FFFFFF\",\n verticalAxisPosition: chartData.verticalAxisPosition,\n legendPosition: chartData.legendPosition,\n stacked: chartData.stacked || false,\n aggregated: false,\n labelsAsText: false,\n };\n }\n\n /**\n * Match external reference (ex. '[1]Sheet 3'!$B$4)\n *\n * First match group is the external reference id\n * Second match group is the sheet id\n * Third match group is the reference of the cell\n */\n const externalReferenceRegex = new RegExp(/'?\\[([0-9]*)\\](.*)'?!(\\$?[a-zA-Z]*\\$?[0-9]*)/g);\n const subtotalRegex = new RegExp(/SUBTOTAL\\(([0-9]*),/g);\n const cellRegex = new RegExp(cellReference.source, \"ig\");\n function convertFormulasContent(sheet, data) {\n const sfMap = getSharedFormulasMap(sheet);\n for (let cell of sheet.rows.map((row) => row.cells).flat()) {\n if (cell === null || cell === void 0 ? void 0 : cell.formula) {\n cell.formula.content =\n cell.formula.sharedIndex !== undefined && !cell.formula.content\n ? \"=\" + adaptFormula(cell.xc, sfMap[cell.formula.sharedIndex])\n : \"=\" + cell.formula.content;\n cell.formula.content = convertFormula(cell.formula.content, data);\n }\n }\n }\n function getSharedFormulasMap(sheet) {\n const formulas = {};\n for (let row of sheet.rows) {\n for (let cell of row.cells) {\n if (cell.formula && cell.formula.sharedIndex !== undefined && cell.formula.content) {\n formulas[cell.formula.sharedIndex] = { refCellXc: cell.xc, formula: cell.formula.content };\n }\n }\n }\n return formulas;\n }\n /**\n * Convert an XLSX formula into something we can evaluate.\n * - remove _xlfn. flags before function names\n * - convert the SUBTOTAL(index, formula) function to the function given by its index\n * - change #REF! into #REF\n * - convert external references into their value\n */\n function convertFormula(formula, data) {\n formula = formula.replace(\"_xlfn.\", \"\");\n formula = formula.replace(/#REF!/g, \"#REF\");\n // SUBOTOTAL function, eg. =SUBTOTAL(3, {formula})\n formula = formula.replace(subtotalRegex, (match, functionId) => {\n const convertedFunction = SUBTOTAL_FUNCTION_CONVERSION_MAP[functionId];\n return convertedFunction ? convertedFunction + \"(\" : match;\n });\n // External references, eg. ='[1]Sheet 3'!$B$4\n formula = formula.replace(externalReferenceRegex, (match, externalRefId, sheetName, cellRef) => {\n var _a;\n externalRefId = Number(externalRefId) - 1;\n cellRef = cellRef.replace(/\\$/g, \"\");\n const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => name === sheetName);\n if (sheetIndex === -1) {\n return match;\n }\n const externalDataset = (_a = data.externalBooks[externalRefId].datasets.find((dataset) => dataset.sheetId === sheetIndex)) === null || _a === void 0 ? void 0 : _a.data;\n if (!externalDataset) {\n return match;\n }\n const datasetValue = externalDataset && externalDataset[cellRef];\n const convertedValue = Number(datasetValue) ? datasetValue : `\"${datasetValue}\"`;\n return convertedValue || match;\n });\n return formula;\n }\n /**\n * Transform a shared formula for the given target.\n *\n * This will compute the offset between the original cell of the shared formula and the target cell,\n * then apply this offset to all the ranges in the formula (taking fixed references into account)\n */\n function adaptFormula(targetCell, sf) {\n const refPosition = toCartesian(sf.refCellXc);\n let newFormula = sf.formula.slice();\n let match;\n do {\n match = cellRegex.exec(newFormula);\n if (match) {\n const formulaPosition = toCartesian(match[0].replace(\"$\", \"\"));\n const targetPosition = toCartesian(targetCell);\n const rangePart = {\n colFixed: match[0].startsWith(\"$\"),\n rowFixed: match[0].includes(\"$\", 1),\n };\n const offset = {\n col: targetPosition.col - refPosition.col,\n row: targetPosition.row - refPosition.row,\n };\n const offsettedPosition = {\n col: rangePart.colFixed ? formulaPosition.col : formulaPosition.col + offset.col,\n row: rangePart.rowFixed ? formulaPosition.row : formulaPosition.row + offset.row,\n };\n newFormula =\n newFormula.slice(0, match.index) +\n toXC(offsettedPosition.col, offsettedPosition.row, rangePart) +\n newFormula.slice(match.index + match[0].length);\n }\n } while (match);\n return newFormula;\n }\n\n function convertSheets(data, warningManager) {\n return data.sheets.map((sheet) => {\n convertFormulasContent(sheet, data);\n const sheetDims = getSheetDims(sheet);\n const sheetOptions = sheet.sheetViews[0];\n return {\n id: sheet.sheetName,\n areGridLinesVisible: sheetOptions ? sheetOptions.showGridLines : true,\n name: sheet.sheetName,\n colNumber: sheetDims[0],\n rowNumber: sheetDims[1],\n cells: convertCells(sheet, data, sheetDims, warningManager),\n merges: sheet.merges,\n cols: convertCols(sheet, sheetDims[0]),\n rows: convertRows(sheet, sheetDims[1]),\n conditionalFormats: convertConditionalFormats(sheet.cfs, data.dxfs, warningManager),\n figures: convertFigures(sheet),\n isVisible: sheet.isVisible,\n panes: sheetOptions\n ? { xSplit: sheetOptions.pane.xSplit, ySplit: sheetOptions.pane.ySplit }\n : { xSplit: 0, ySplit: 0 },\n filterTables: [],\n };\n });\n }\n function convertCols(sheet, numberOfCols) {\n var _a;\n const cols = {};\n // Excel begins indexes at 1\n for (let i = 1; i < numberOfCols + 1; i++) {\n const col = sheet.cols.find((col) => col.min <= i && i <= col.max);\n let colSize;\n if (col && col.width)\n colSize = col.width;\n else if ((_a = sheet.sheetFormat) === null || _a === void 0 ? void 0 : _a.defaultColWidth)\n colSize = sheet.sheetFormat.defaultColWidth;\n else\n colSize = EXCEL_DEFAULT_COL_WIDTH;\n cols[i - 1] = { size: convertWidthFromExcel(colSize), isHidden: col === null || col === void 0 ? void 0 : col.hidden };\n }\n return cols;\n }\n function convertRows(sheet, numberOfRows) {\n var _a;\n const rows = {};\n // Excel begins indexes at 1\n for (let i = 1; i < numberOfRows + 1; i++) {\n const row = sheet.rows.find((row) => row.index === i);\n let rowSize;\n if (row && row.height)\n rowSize = row.height;\n else if ((_a = sheet.sheetFormat) === null || _a === void 0 ? void 0 : _a.defaultRowHeight)\n rowSize = sheet.sheetFormat.defaultRowHeight;\n else\n rowSize = EXCEL_DEFAULT_ROW_HEIGHT;\n rows[i - 1] = { size: convertHeightFromExcel(rowSize), isHidden: row === null || row === void 0 ? void 0 : row.hidden };\n }\n return rows;\n }\n /** Remove newlines (\\n) in shared strings, We do not support them */\n function convertSharedStrings(xlsxSharedStrings) {\n return xlsxSharedStrings.map((str) => str.replace(/\\n/g, \"\"));\n }\n function convertCells(sheet, data, sheetDims, warningManager) {\n const cells = {};\n const sharedStrings = convertSharedStrings(data.sharedStrings);\n const hyperlinkMap = sheet.hyperlinks.reduce((map, link) => {\n map[link.xc] = link;\n return map;\n }, {});\n for (let row of sheet.rows) {\n for (let cell of row.cells) {\n cells[cell.xc] = {\n content: getCellValue(cell, hyperlinkMap, sharedStrings, warningManager),\n // + 1 : our indexes for normalized values begin at 1 and not 0\n style: cell.styleIndex ? cell.styleIndex + 1 : undefined,\n border: cell.styleIndex ? data.styles[cell.styleIndex].borderId + 1 : undefined,\n format: cell.styleIndex ? data.styles[cell.styleIndex].numFmtId + 1 : undefined,\n };\n }\n }\n // Apply row style\n for (let row of sheet.rows.filter((row) => row.styleIndex)) {\n for (let colIndex = 1; colIndex <= sheetDims[0]; colIndex++) {\n const xc = toXC(colIndex - 1, row.index - 1); // Excel indexes start at 1\n let cell = cells[xc];\n if (!cell) {\n cell = {};\n cells[xc] = cell;\n }\n cell.style = cell.style ? cell.style : row.styleIndex + 1;\n cell.border = cell.border ? cell.border : data.styles[row.styleIndex].borderId + 1;\n cell.format = cell.format ? cell.format : data.styles[row.styleIndex].numFmtId + 1;\n }\n }\n // Apply col style\n for (let col of sheet.cols.filter((col) => col.styleIndex)) {\n for (let colIndex = col.min; colIndex <= Math.min(col.max, sheetDims[0]); colIndex++) {\n for (let rowIndex = 1; rowIndex <= sheetDims[1]; rowIndex++) {\n const xc = toXC(colIndex - 1, rowIndex - 1); // Excel indexes start at 1\n let cell = cells[xc];\n if (!cell) {\n cell = {};\n cells[xc] = cell;\n }\n cell.style = cell.style ? cell.style : col.styleIndex + 1;\n cell.border = cell.border ? cell.border : data.styles[col.styleIndex].borderId + 1;\n cell.format = cell.format ? cell.format : data.styles[col.styleIndex].numFmtId + 1;\n }\n }\n }\n return cells;\n }\n function getCellValue(cell, hyperLinksMap, sharedStrings, warningManager) {\n let cellValue;\n switch (cell.type) {\n case \"sharedString\":\n const ssIndex = parseInt(cell.value, 10);\n cellValue = sharedStrings[ssIndex];\n break;\n case \"boolean\":\n cellValue = Number(cell.value) ? \"TRUE\" : \"FALSE\";\n break;\n case \"date\": // I'm not sure where this is used rather than a number with a format\n case \"error\": // I don't think Excel really uses this\n case \"inlineStr\":\n case \"number\":\n case \"str\":\n cellValue = cell.value;\n break;\n }\n if (cellValue && hyperLinksMap[cell.xc]) {\n cellValue = convertHyperlink(hyperLinksMap[cell.xc], cellValue, warningManager);\n }\n if (cell.formula) {\n cellValue = cell.formula.content;\n }\n return cellValue;\n }\n function convertHyperlink(link, cellValue, warningManager) {\n const label = link.display || cellValue;\n if (!link.relTarget && !link.location) {\n warningManager.generateNotSupportedWarning(WarningTypes.BadlyFormattedHyperlink);\n }\n const url = link.relTarget\n ? link.relTarget\n : buildSheetLink(splitReference(link.location).sheetName);\n return markdownLink(label, url);\n }\n function getSheetDims(sheet) {\n const dims = [0, 0];\n for (let row of sheet.rows) {\n dims[0] = Math.max(dims[0], ...row.cells.map((cell) => toCartesian(cell.xc).col));\n dims[1] = Math.max(dims[1], row.index);\n }\n dims[0] = Math.max(dims[0], EXCEL_IMPORT_DEFAULT_NUMBER_OF_COLS);\n dims[1] = Math.max(dims[1], EXCEL_IMPORT_DEFAULT_NUMBER_OF_ROWS);\n return dims;\n }\n\n const TABLE_HEADER_STYLE = {\n fillColor: \"#000000\",\n textColor: \"#ffffff\",\n bold: true,\n };\n const TABLE_HIGHLIGHTED_CELL_STYLE = {\n bold: true,\n };\n const TABLE_BORDER_STYLE = [\"thin\", \"#000000FF\"];\n /**\n * Convert the imported XLSX tables.\n *\n * We will create a FilterTable if the imported table have filters, then apply a style in all the cells of the table\n * and convert the table-specific formula references into standard references.\n *\n * Change the converted data in-place.\n */\n function convertTables(convertedData, xlsxData) {\n for (const xlsxSheet of xlsxData.sheets) {\n for (const table of xlsxSheet.tables) {\n const sheet = convertedData.sheets.find((sheet) => sheet.name === xlsxSheet.sheetName);\n if (!sheet || !table.autoFilter)\n continue;\n if (!sheet.filterTables)\n sheet.filterTables = [];\n sheet.filterTables.push({ range: table.ref });\n }\n }\n applyTableStyle(convertedData, xlsxData);\n convertTableFormulaReferences(convertedData.sheets, xlsxData.sheets);\n }\n /**\n * Apply a style to all the cells that are in a table, and add the created styles in the converted data.\n *\n * In XLSXs, the style of the cells of a table are not directly in the sheet, but rather deduced from the style of\n * the table that is defined in the table's XML file. The style of the table is a string referencing a standard style\n * defined in the OpenXML specifications. As there are 80+ different styles, we won't implement every one of them but\n * we will just define a style that will be used for all the imported tables.\n */\n function applyTableStyle(convertedData, xlsxData) {\n var _a, _b, _c, _d;\n const styles = objectToArray(convertedData.styles);\n const borders = objectToArray(convertedData.borders);\n for (let xlsxSheet of xlsxData.sheets) {\n for (let table of xlsxSheet.tables) {\n const sheet = convertedData.sheets.find((sheet) => sheet.name === xlsxSheet.sheetName);\n if (!sheet)\n continue;\n const tableZone = toZone(table.ref);\n // Table style\n for (let i = 0; i < table.headerRowCount; i++) {\n applyStyleToZone(TABLE_HEADER_STYLE, { ...tableZone, bottom: tableZone.top + i }, sheet.cells, styles);\n }\n for (let i = 0; i < table.totalsRowCount; i++) {\n applyStyleToZone(TABLE_HIGHLIGHTED_CELL_STYLE, { ...tableZone, top: tableZone.bottom - i }, sheet.cells, styles);\n }\n if ((_a = table.style) === null || _a === void 0 ? void 0 : _a.showFirstColumn) {\n applyStyleToZone(TABLE_HIGHLIGHTED_CELL_STYLE, { ...tableZone, right: tableZone.left }, sheet.cells, styles);\n }\n if ((_b = table.style) === null || _b === void 0 ? void 0 : _b.showLastColumn) {\n applyStyleToZone(TABLE_HIGHLIGHTED_CELL_STYLE, { ...tableZone, left: tableZone.right }, sheet.cells, styles);\n }\n // Table borders\n // Borders at : table outline + col(/row) if showColumnStripes(/showRowStripes) + border above totalRow\n for (let col = tableZone.left; col <= tableZone.right; col++) {\n for (let row = tableZone.top; row <= tableZone.bottom; row++) {\n const xc = toXC(col, row);\n const cell = sheet.cells[xc];\n const border = {\n left: col === tableZone.left || ((_c = table.style) === null || _c === void 0 ? void 0 : _c.showColumnStripes)\n ? TABLE_BORDER_STYLE\n : undefined,\n right: col === tableZone.right ? TABLE_BORDER_STYLE : undefined,\n top: row === tableZone.top ||\n ((_d = table.style) === null || _d === void 0 ? void 0 : _d.showRowStripes) ||\n row > tableZone.bottom - table.totalsRowCount\n ? TABLE_BORDER_STYLE\n : undefined,\n bottom: row === tableZone.bottom ? TABLE_BORDER_STYLE : undefined,\n };\n const newBorder = (cell === null || cell === void 0 ? void 0 : cell.border) ? { ...borders[cell.border], ...border } : border;\n let borderIndex = borders.findIndex((border) => deepEquals(border, newBorder));\n if (borderIndex === -1) {\n borderIndex = borders.length;\n borders.push(newBorder);\n }\n if (cell) {\n cell.border = borderIndex;\n }\n else {\n sheet.cells[xc] = { border: borderIndex };\n }\n }\n }\n }\n }\n convertedData.styles = arrayToObject(styles);\n convertedData.borders = arrayToObject(borders);\n }\n /**\n * Apply a style to all the cells in the zone. The applied style WILL NOT overwrite values in existing style of the cell.\n *\n * If a style that was not in the styles array was applied, push it into the style array.\n */\n function applyStyleToZone(appliedStyle, zone, cells, styles) {\n for (let col = zone.left; col <= zone.right; col++) {\n for (let row = zone.top; row <= zone.bottom; row++) {\n const xc = toXC(col, row);\n const cell = cells[xc];\n const newStyle = (cell === null || cell === void 0 ? void 0 : cell.style) ? { ...styles[cell.style], ...appliedStyle } : appliedStyle;\n let styleIndex = styles.findIndex((style) => deepEquals(style, newStyle));\n if (styleIndex === -1) {\n styleIndex = styles.length;\n styles.push(newStyle);\n }\n if (cell) {\n cell.style = styleIndex;\n }\n else {\n cells[xc] = { style: styleIndex };\n }\n }\n }\n }\n /**\n * In all the sheets, replace the table-only references in the formula cells with standard references.\n */\n function convertTableFormulaReferences(convertedSheets, xlsxSheets) {\n for (let sheet of convertedSheets) {\n const tables = xlsxSheets.find((s) => s.sheetName === sheet.name).tables;\n for (let table of tables) {\n const tabRef = table.name + \"[\";\n for (let position of positions(toZone(table.ref))) {\n const xc = toXC(position.col, position.row);\n const cell = sheet.cells[xc];\n if (cell && cell.content && cell.content.startsWith(\"=\")) {\n let refIndex;\n while ((refIndex = cell.content.indexOf(tabRef)) !== -1) {\n let reference = cell.content.slice(refIndex + tabRef.length);\n // Expression can either be tableName[colName] or tableName[[#This Row], [colName]]\n let endIndex = reference.indexOf(\"]\");\n if (reference.startsWith(`[`)) {\n endIndex = reference.indexOf(\"]\", endIndex + 1);\n endIndex = reference.indexOf(\"]\", endIndex + 1);\n }\n reference = reference.slice(0, endIndex);\n const convertedRef = convertTableReference(reference, table, xc);\n cell.content =\n cell.content.slice(0, refIndex) +\n convertedRef +\n cell.content.slice(tabRef.length + refIndex + endIndex + 1);\n }\n }\n }\n }\n }\n }\n /**\n * Convert table-specific references in formulas into standard references.\n *\n * A reference in a table can have the form (only the part between brackets should be given to this function):\n * - tableName[colName] : reference to the whole column \"colName\"\n * - tableName[[#keyword], [colName]] : reference to some of the element(s) of the column colName\n *\n * The available keywords are :\n * - #All : all the column (including totals)\n * - #Data : only the column data (no headers/totals)\n * - #Headers : only the header of the column\n * - #Totals : only the totals of the column\n * - #This Row : only the element in the same row as the cell\n */\n function convertTableReference(expr, table, cellXc) {\n const refElements = expr.split(\",\");\n const tableZone = toZone(table.ref);\n const refZone = { ...tableZone };\n let isReferencedZoneValid = true;\n // Single column reference\n if (refElements.length === 1) {\n const colRelativeIndex = table.cols.findIndex((col) => col.name === refElements[0]);\n refZone.left = refZone.right = colRelativeIndex + tableZone.left;\n if (table.headerRowCount) {\n refZone.top += table.headerRowCount;\n }\n if (table.totalsRowCount) {\n refZone.bottom -= 1;\n }\n }\n // Other references\n else {\n switch (refElements[0].slice(1, refElements[0].length - 1)) {\n case \"#All\":\n refZone.top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;\n refZone.bottom = tableZone.bottom;\n break;\n case \"#Data\":\n refZone.top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;\n refZone.bottom = table.totalsRowCount ? tableZone.bottom + 1 : tableZone.bottom;\n break;\n case \"#This Row\":\n refZone.top = refZone.bottom = toCartesian(cellXc).row;\n break;\n case \"#Headers\":\n refZone.top = refZone.bottom = tableZone.top;\n if (!table.headerRowCount) {\n isReferencedZoneValid = false;\n }\n break;\n case \"#Totals\":\n refZone.top = refZone.bottom = tableZone.bottom;\n if (!table.totalsRowCount) {\n isReferencedZoneValid = false;\n }\n break;\n }\n const colRef = refElements[1].slice(1, refElements[1].length - 1);\n const colRelativeIndex = table.cols.findIndex((col) => col.name === colRef);\n refZone.left = refZone.right = colRelativeIndex + tableZone.left;\n }\n if (!isReferencedZoneValid) {\n return INCORRECT_RANGE_STRING;\n }\n return refZone.top !== refZone.bottom ? zoneToXc(refZone) : toXC(refZone.left, refZone.top);\n }\n\n // -------------------------------------\n // XML HELPERS\n // -------------------------------------\n function createXMLFile(doc, path, contentType) {\n return {\n content: new XMLSerializer().serializeToString(doc),\n path,\n contentType,\n };\n }\n function xmlEscape(str) {\n return String(str)\n .replace(/\\&/g, \"&\")\n .replace(/\\/g, \">\")\n .replace(/\\\"/g, \""\")\n .replace(/\\'/g, \"'\");\n }\n function formatAttributes(attrs) {\n return new XMLString(attrs.map(([key, val]) => `${key}=\"${xmlEscape(val)}\"`).join(\" \"));\n }\n function parseXML(xmlString, mimeType = \"text/xml\") {\n const document = new DOMParser().parseFromString(xmlString.toString(), mimeType);\n const parserError = document.querySelector(\"parsererror\");\n if (parserError) {\n const errorString = parserError.innerHTML;\n const lineNumber = parseInt(errorString.split(\":\")[0], 10);\n const xmlStringArray = xmlString.toString().trim().split(\"\\n\");\n const xmlPreview = xmlStringArray\n .slice(Math.max(lineNumber - 3, 0), Math.min(lineNumber + 2, xmlStringArray.length))\n .join(\"\\n\");\n throw new Error(`XML string could not be parsed: ${errorString}\\n${xmlPreview}`);\n }\n return document;\n }\n function getDefaultXLSXStructure() {\n return {\n relsFiles: [],\n sharedStrings: [],\n // default Values that will always be part of the style sheet\n styles: [\n {\n fontId: 0,\n fillId: 0,\n numFmtId: 0,\n borderId: 0,\n alignment: { vertical: \"center\" },\n },\n ],\n fonts: [\n {\n size: DEFAULT_FONT_SIZE,\n family: 2,\n color: { rgb: \"000000\" },\n name: \"Calibri\",\n },\n ],\n fills: [{ reservedAttribute: \"none\" }, { reservedAttribute: \"gray125\" }],\n borders: [{}],\n numFmts: [],\n dxfs: [],\n };\n }\n function createOverride(partName, contentType) {\n return escapeXml /*xml*/ `\n \n `;\n }\n function joinXmlNodes(xmlNodes) {\n return new XMLString(xmlNodes.join(\"\\n\"));\n }\n /**\n * Escape interpolated values except if the value is already\n * a properly escaped XML string.\n *\n * ```\n * escapeXml`${\"This will be escaped\"}`\n * ```\n */\n function escapeXml(strings, ...expressions) {\n let str = [strings[0]];\n for (let i = 0; i < expressions.length; i++) {\n const value = expressions[i] instanceof XMLString ? expressions[i] : xmlEscape(expressions[i]);\n str.push(value + strings[i + 1]);\n }\n return new XMLString(concat(str));\n }\n /**\n * Removes the namespace of all the xml tags in the string.\n *\n * Eg. : \"ns:test a\" => \"test a\"\n */\n function removeNamespaces(query) {\n return query.replace(/[a-z0-9]+:(?=[a-z0-9]+)/gi, \"\");\n }\n /**\n * Escape the namespace's colons of all the xml tags in the string.\n *\n * Eg. : \"ns:test a\" => \"ns\\\\:test a\"\n */\n function escapeNamespaces(query) {\n return query.replace(/([a-z0-9]+):(?=[a-z0-9]+)/gi, \"$1\\\\:\");\n }\n /**\n * Return true if the querySelector ignores the namespaces when searching for a tag in the DOM.\n *\n * Should return true if it's running on a browser, and false if it's running on jest (jsdom).\n */\n function areNamespaceIgnoredByQuerySelector() {\n const doc = new DOMParser().parseFromString(\"\", \"text/xml\");\n return doc.querySelector(\"test\") !== null;\n }\n\n class AttributeValue {\n constructor(value) {\n this.value = value;\n }\n asString() {\n return fixXlsxUnicode(String(this.value));\n }\n asBool() {\n return Boolean(Number(this.value));\n }\n asNum() {\n return Number(this.value);\n }\n }\n class XlsxBaseExtractor {\n constructor(rootFile, xlsxStructure, warningManager) {\n // The xml file we are currently parsing. We should have one Extractor class by XLSXImportFile, but\n // the XLSXImportFile contains both the main .xml file, and the .rels file\n this.currentFile = undefined;\n this.rootFile = rootFile;\n this.currentFile = rootFile.file.fileName;\n this.xlsxFileStructure = xlsxStructure;\n this.warningManager = warningManager;\n this.areNamespaceIgnored = areNamespaceIgnoredByQuerySelector();\n this.relationships = {};\n if (rootFile.rels) {\n this.extractRelationships(rootFile.rels).map((rel) => {\n this.relationships[rel.id] = rel;\n });\n }\n }\n /**\n * Extract all the relationships inside a .xml.rels file\n */\n extractRelationships(relFile) {\n return this.mapOnElements({ parent: relFile.xml, query: \"Relationship\" }, (relationshipElement) => {\n return {\n id: this.extractAttr(relationshipElement, \"Id\", { required: true }).asString(),\n target: this.extractAttr(relationshipElement, \"Target\", { required: true }).asString(),\n type: this.extractAttr(relationshipElement, \"Type\", { required: true }).asString(),\n };\n });\n }\n /**\n * Get the list of all the XLSX files in the XLSX file structure\n */\n getListOfFiles() {\n const files = Object.values(this.xlsxFileStructure).flat().filter(isDefined$1);\n return files;\n }\n /**\n * Return an array containing the return value of the given function applied to all the XML elements\n * found using the MapOnElementArgs.\n *\n * The arguments contains :\n * - query : a QuerySelector string to find the elements to apply the function to\n * - parent : an XML element or XML document in which to find the queried elements\n * - children : if true, the function is applied on the direct children of the queried element\n *\n * This method will also handle the errors thrown in the argument function.\n */\n mapOnElements(args, fct) {\n var _a;\n const ret = [];\n const oldWorkingDocument = this.currentFile;\n let elements;\n if (args.children) {\n const children = (_a = this.querySelector(args.parent, args.query)) === null || _a === void 0 ? void 0 : _a.children;\n elements = children ? children : [];\n }\n else {\n elements = this.querySelectorAll(args.parent, args.query);\n }\n if (elements) {\n for (let element of elements) {\n try {\n ret.push(fct(element));\n }\n catch (e) {\n this.catchErrorOnElement(e, element);\n }\n }\n }\n this.currentFile = oldWorkingDocument;\n return ret;\n }\n /**\n * Log an error caught when parsing an element in the warningManager.\n */\n catchErrorOnElement(error, onElement) {\n const errorMsg = onElement\n ? `Error when parsing an element <${onElement.tagName}> of file ${this.currentFile}, skip this element. \\n${error.stack}`\n : `Error when parsing file ${this.currentFile}.`;\n this.warningManager.addParsingWarning([errorMsg, error.message].join(\"\\n\"));\n }\n /**\n * Extract an attribute from an Element.\n *\n * If the attribute is required but was not found, will add a warning in the warningManager if it was given a default\n * value, and throw an error if no default value was given.\n *\n * Can only return undefined value for non-required attributes without default value.\n */\n extractAttr(e, attName, optionalArgs) {\n const attribute = e.attributes[attName];\n if (!attribute)\n this.handleMissingValue(e, `attribute \"${attName}\"`, optionalArgs);\n const value = (attribute === null || attribute === void 0 ? void 0 : attribute.value) ? attribute.value : optionalArgs === null || optionalArgs === void 0 ? void 0 : optionalArgs.default;\n return (value === undefined ? undefined : new AttributeValue(value));\n }\n /**\n * Extract the text content of an Element.\n *\n * If the text content is required but was not found, will add a warning in the warningManager if it was given a default\n * value, and throw an error if no default value was given.\n *\n * Can only return undefined value for non-required text content without default value.\n */\n extractTextContent(element, optionalArgs) {\n var _a;\n if ((optionalArgs === null || optionalArgs === void 0 ? void 0 : optionalArgs.default) !== undefined && typeof optionalArgs.default !== \"string\") {\n throw new Error(\"extractTextContent default value should be a string\");\n }\n const shouldPreserveSpaces = ((_a = element === null || element === void 0 ? void 0 : element.attributes[\"xml:space\"]) === null || _a === void 0 ? void 0 : _a.value) === \"preserve\";\n let textContent = element === null || element === void 0 ? void 0 : element.textContent;\n if (!element || textContent === null) {\n this.handleMissingValue(element, `text content`, optionalArgs);\n }\n if (textContent) {\n textContent = shouldPreserveSpaces ? textContent : textContent.trim();\n }\n return (textContent ? fixXlsxUnicode(textContent) : optionalArgs === null || optionalArgs === void 0 ? void 0 : optionalArgs.default);\n }\n /**\n * Extract an attribute of a child of the given element.\n *\n * The reference of a child can be a string (tag of the child) or an number (index in the list of children of the element)\n *\n * If the attribute is required but either the attribute or the referenced child element was not found, it will\n * will add a warning in the warningManager if it was given a default value, and throw an error if no default value was given.\n *\n * Can only return undefined value for non-required attributes without default value.\n */\n extractChildAttr(e, childRef, attName, optionalArgs) {\n var _a;\n let child;\n if (typeof childRef === \"number\") {\n child = e.children[childRef];\n }\n else {\n child = this.querySelector(e, childRef);\n }\n if (!child) {\n this.handleMissingValue(e, typeof childRef === \"number\" ? `child at index ${childRef}` : `child <${childRef}>`, optionalArgs);\n }\n const value = child\n ? (_a = this.extractAttr(child, attName, optionalArgs)) === null || _a === void 0 ? void 0 : _a.asString()\n : optionalArgs === null || optionalArgs === void 0 ? void 0 : optionalArgs.default;\n return (value !== undefined ? new AttributeValue(value) : undefined);\n }\n /**\n * Extract the text content of a child of the given element.\n *\n * If the text content is required but either the text content or the referenced child element was not found, it will\n * will add a warning in the warningManager if it was given a default value, and throw an error if no default value was given.\n *\n * Can only return undefined value for non-required text content without default value.\n */\n extractChildTextContent(e, childRef, optionalArgs) {\n if ((optionalArgs === null || optionalArgs === void 0 ? void 0 : optionalArgs.default) !== undefined && typeof optionalArgs.default !== \"string\") {\n throw new Error(\"extractTextContent default value should be a string\");\n }\n let child = this.querySelector(e, childRef);\n if (!child) {\n this.handleMissingValue(e, `child <${childRef}>`, optionalArgs);\n }\n return (child ? this.extractTextContent(child, optionalArgs) : optionalArgs === null || optionalArgs === void 0 ? void 0 : optionalArgs.default);\n }\n /**\n * Should be called if a extractAttr/extractTextContent doesn't find the element it needs to extract.\n *\n * If the extractable was required, this function will add a warning in the warningManager if there was a default value,\n * and throw an error if no default value was given.\n */\n handleMissingValue(parentElement, missingElementName, optionalArgs) {\n if (optionalArgs === null || optionalArgs === void 0 ? void 0 : optionalArgs.required) {\n if (optionalArgs === null || optionalArgs === void 0 ? void 0 : optionalArgs.default) {\n this.warningManager.addParsingWarning(`Missing required ${missingElementName} in element <${parentElement.tagName}> of ${this.currentFile}, replacing it by the default value ${optionalArgs.default}`);\n }\n else {\n throw new Error(`Missing required ${missingElementName} in element <${parentElement.tagName}> of ${this.currentFile}, and no default value was set`);\n }\n }\n }\n /**\n * Extract a color, extracting it from the theme if needed.\n *\n * Will throw an error if the element references a theme, but no theme was provided or the theme it doesn't contain the color.\n */\n extractColor(colorElement, theme, defaultColor) {\n var _a, _b, _c, _d, _e;\n if (!colorElement) {\n return defaultColor ? { rgb: defaultColor } : undefined;\n }\n const themeIndex = (_a = this.extractAttr(colorElement, \"theme\")) === null || _a === void 0 ? void 0 : _a.asString();\n let rgb;\n if (themeIndex !== undefined) {\n if (!theme || !theme.clrScheme) {\n throw new Error(\"Color referencing a theme but no theme was provided\");\n }\n rgb = this.getThemeColor(themeIndex, theme.clrScheme);\n }\n else {\n rgb = (_b = this.extractAttr(colorElement, \"rgb\")) === null || _b === void 0 ? void 0 : _b.asString();\n }\n const color = {\n rgb,\n auto: (_c = this.extractAttr(colorElement, \"auto\")) === null || _c === void 0 ? void 0 : _c.asBool(),\n indexed: (_d = this.extractAttr(colorElement, \"indexed\")) === null || _d === void 0 ? void 0 : _d.asNum(),\n tint: (_e = this.extractAttr(colorElement, \"tint\")) === null || _e === void 0 ? void 0 : _e.asNum(),\n };\n return color;\n }\n /**\n * Returns the xlsx file targeted by a relationship.\n */\n getTargetXmlFile(relationship) {\n if (!relationship)\n throw new Error(\"Undefined target file\");\n let target = relationship.target;\n target = target.replace(\"../\", \"\");\n target = target.replace(\"./\", \"\");\n // Use \"endsWith\" because targets are relative paths, and we know the files by their absolute path.\n const f = this.getListOfFiles().find((f) => f.file.fileName.endsWith(target));\n if (!f || !f.file)\n throw new Error(\"Cannot find target file\");\n return f;\n }\n /**\n * Wrapper of querySelector, but we'll remove the namespaces from the query if areNamespacesIgnored is true.\n *\n * Why we need to do this :\n * - For an XML \"\"\n * - on Jest(jsdom) : xml.querySelector(\"test\") == null, xml.querySelector(\"t\\\\:test\") == \n * - on Browser : xml.querySelector(\"test\") == , xml.querySelector(\"t\\\\:test\") == null\n */\n querySelector(element, query) {\n query = this.areNamespaceIgnored ? removeNamespaces(query) : escapeNamespaces(query);\n return element.querySelector(query);\n }\n /**\n * Wrapper of querySelectorAll, but we'll remove the namespaces from the query if areNamespacesIgnored is true.\n *\n * Why we need to do this :\n * - For an XML \"\"\n * - on Jest(jsdom) : xml.querySelectorAll(\"test\") == [], xml.querySelectorAll(\"t\\\\:test\") == []\n * - on Browser : xml.querySelectorAll(\"test\") == [], xml.querySelectorAll(\"t\\\\:test\") == []\n */\n querySelectorAll(element, query) {\n query = this.areNamespaceIgnored ? removeNamespaces(query) : escapeNamespaces(query);\n return element.querySelectorAll(query);\n }\n /**\n * Get a color from its id in the Theme's colorScheme.\n *\n * Note that Excel don't use the colors from the theme but from its own internal theme, so the displayed\n * colors will be different in the import than in excel.\n * .\n */\n getThemeColor(colorId, clrScheme) {\n switch (colorId) {\n case \"0\": // 0 : sysColor window text\n return \"FFFFFF\";\n case \"1\": // 1 : sysColor window background\n return \"000000\";\n // Don't ask me why these 2 are inverted, I cannot find any documentation for it but everyone does it\n case \"2\":\n return clrScheme[\"3\"].value;\n case \"3\":\n return clrScheme[\"2\"].value;\n default:\n return clrScheme[colorId].value;\n }\n }\n }\n\n /**\n * XLSX Extractor class that can be used for either sharedString XML files or theme XML files.\n *\n * Since they both are quite simple, it make sense to make a single class to manage them all, to avoid unnecessary file\n * cluttering.\n */\n class XlsxMiscExtractor extends XlsxBaseExtractor {\n getTheme() {\n const clrScheme = this.mapOnElements({ query: \"a:clrScheme\", parent: this.rootFile.file.xml, children: true }, (element) => {\n return {\n name: element.tagName,\n value: this.extractChildAttr(element, 0, \"val\", {\n required: true,\n default: AUTO_COLOR,\n }).asString(),\n lastClr: this.extractChildAttr(element, 0, \"lastClr\", {\n default: AUTO_COLOR,\n }).asString(),\n };\n });\n return { clrScheme };\n }\n /**\n * Get the array of shared strings of the XLSX.\n *\n * Worth noting that running a prettier on the xml can mess up some strings, since there is an option in the\n * xmls to keep the spacing and not trim the string.\n */\n getSharedStrings() {\n return this.mapOnElements({ parent: this.rootFile.file.xml, query: \"si\" }, (ssElement) => {\n // Shared string can either be a simple text, or a rich text (text with formatting, possibly in multiple parts)\n if (ssElement.children[0].tagName === \"t\") {\n return this.extractTextContent(ssElement) || \"\";\n }\n // We don't support rich text formatting, we'll only extract the text\n else {\n return this.mapOnElements({ parent: ssElement, query: \"t\" }, (textElement) => {\n return this.extractTextContent(textElement) || \"\";\n }).join(\"\");\n }\n });\n }\n }\n\n class XlsxCfExtractor extends XlsxBaseExtractor {\n constructor(sheetFile, xlsxStructure, warningManager, theme) {\n super(sheetFile, xlsxStructure, warningManager);\n this.theme = theme;\n }\n extractConditionalFormattings() {\n const cfs = this.mapOnElements({ parent: this.rootFile.file.xml, query: \"worksheet > conditionalFormatting\" }, (cfElement) => {\n var _a;\n return {\n // sqref = ranges on which the cf applies, separated by spaces\n sqref: this.extractAttr(cfElement, \"sqref\", { required: true }).asString().split(\" \"),\n pivot: (_a = this.extractAttr(cfElement, \"pivot\")) === null || _a === void 0 ? void 0 : _a.asBool(),\n cfRules: this.extractCFRules(cfElement, this.theme),\n };\n });\n // XLSX extension to OpenXml\n cfs.push(...this.mapOnElements({ parent: this.rootFile.file.xml, query: \"extLst x14:conditionalFormatting\" }, (cfElement) => {\n var _a;\n return {\n sqref: this.extractChildTextContent(cfElement, \"xm:sqref\", { required: true }).split(\" \"),\n pivot: (_a = this.extractAttr(cfElement, \"xm:pivot\")) === null || _a === void 0 ? void 0 : _a.asBool(),\n cfRules: this.extractCFRules(cfElement, this.theme),\n };\n }));\n return cfs;\n }\n extractCFRules(cfElement, theme) {\n return this.mapOnElements({ parent: cfElement, query: \"cfRule, x14:cfRule\" }, (cfRuleElement) => {\n var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;\n const cfType = this.extractAttr(cfRuleElement, \"type\", {\n required: true,\n }).asString();\n if (cfType === \"dataBar\") {\n // Databars are an extension to OpenXml and have a different format (XLSX \u00a72.6.30). Do'nt bother\n // extracting them as we don't support them.\n throw new Error(\"Databars conditional formats are not supported.\");\n }\n return {\n type: cfType,\n priority: this.extractAttr(cfRuleElement, \"priority\", { required: true }).asNum(),\n colorScale: this.extractCfColorScale(cfRuleElement, theme),\n formula: this.extractCfFormula(cfRuleElement),\n iconSet: this.extractCfIconSet(cfRuleElement),\n dxfId: (_a = this.extractAttr(cfRuleElement, \"dxfId\")) === null || _a === void 0 ? void 0 : _a.asNum(),\n stopIfTrue: (_b = this.extractAttr(cfRuleElement, \"stopIfTrue\")) === null || _b === void 0 ? void 0 : _b.asBool(),\n aboveAverage: (_c = this.extractAttr(cfRuleElement, \"aboveAverage\")) === null || _c === void 0 ? void 0 : _c.asBool(),\n percent: (_d = this.extractAttr(cfRuleElement, \"percent\")) === null || _d === void 0 ? void 0 : _d.asBool(),\n bottom: (_e = this.extractAttr(cfRuleElement, \"bottom\")) === null || _e === void 0 ? void 0 : _e.asBool(),\n operator: (_f = this.extractAttr(cfRuleElement, \"operator\")) === null || _f === void 0 ? void 0 : _f.asString(),\n text: (_g = this.extractAttr(cfRuleElement, \"text\")) === null || _g === void 0 ? void 0 : _g.asString(),\n timePeriod: (_h = this.extractAttr(cfRuleElement, \"timePeriod\")) === null || _h === void 0 ? void 0 : _h.asString(),\n rank: (_j = this.extractAttr(cfRuleElement, \"rank\")) === null || _j === void 0 ? void 0 : _j.asNum(),\n stdDev: (_k = this.extractAttr(cfRuleElement, \"stdDev\")) === null || _k === void 0 ? void 0 : _k.asNum(),\n equalAverage: (_l = this.extractAttr(cfRuleElement, \"equalAverage\")) === null || _l === void 0 ? void 0 : _l.asBool(),\n };\n });\n }\n extractCfFormula(cfRulesElement) {\n return this.mapOnElements({ parent: cfRulesElement, query: \"formula\" }, (cfFormulaElements) => {\n return this.extractTextContent(cfFormulaElements, { required: true });\n });\n }\n extractCfColorScale(cfRulesElement, theme) {\n const colorScaleElement = this.querySelector(cfRulesElement, \"colorScale\");\n if (!colorScaleElement)\n return undefined;\n return {\n colors: this.mapOnElements({ parent: colorScaleElement, query: \"color\" }, (colorElement) => {\n return this.extractColor(colorElement, theme, \"ffffff\");\n }),\n cfvos: this.extractCFVos(colorScaleElement),\n };\n }\n extractCfIconSet(cfRulesElement) {\n var _a, _b;\n const iconSetElement = this.querySelector(cfRulesElement, \"iconSet, x14:iconSet\");\n if (!iconSetElement)\n return undefined;\n return {\n iconSet: this.extractAttr(iconSetElement, \"iconSet\", {\n default: \"3TrafficLights1\",\n }).asString(),\n showValue: this.extractAttr(iconSetElement, \"showValue\", { default: true }).asBool(),\n percent: this.extractAttr(iconSetElement, \"percent\", { default: true }).asBool(),\n reverse: (_a = this.extractAttr(iconSetElement, \"reverse\")) === null || _a === void 0 ? void 0 : _a.asBool(),\n custom: (_b = this.extractAttr(iconSetElement, \"custom\")) === null || _b === void 0 ? void 0 : _b.asBool(),\n cfvos: this.extractCFVos(iconSetElement),\n cfIcons: this.extractCfIcons(iconSetElement),\n };\n }\n extractCfIcons(iconSetElement) {\n const icons = this.mapOnElements({ parent: iconSetElement, query: \"cfIcon, x14:cfIcon\" }, (cfIconElement) => {\n return {\n iconSet: this.extractAttr(cfIconElement, \"iconSet\", {\n required: true,\n }).asString(),\n iconId: this.extractAttr(cfIconElement, \"iconId\", { required: true }).asNum(),\n };\n });\n return icons.length === 0 ? undefined : icons;\n }\n extractCFVos(parent) {\n return this.mapOnElements({ parent, query: \"cfvo, x14:cfvo\" }, (cfVoElement) => {\n var _a, _b;\n return {\n type: this.extractAttr(cfVoElement, \"type\", {\n required: true,\n }).asString(),\n gte: (_a = this.extractAttr(cfVoElement, \"gte\", { default: true })) === null || _a === void 0 ? void 0 : _a.asBool(),\n value: cfVoElement.attributes[\"val\"]\n ? (_b = this.extractAttr(cfVoElement, \"val\")) === null || _b === void 0 ? void 0 : _b.asString()\n : this.extractChildTextContent(cfVoElement, \"f, xm:f\"),\n };\n });\n }\n }\n\n class XlsxChartExtractor extends XlsxBaseExtractor {\n extractChart() {\n return this.mapOnElements({ parent: this.rootFile.file.xml, query: \"c:chartSpace\" }, (rootChartElement) => {\n const chartType = this.getChartType(rootChartElement);\n if (!CHART_TYPE_CONVERSION_MAP[chartType]) {\n throw new Error(`Unsupported chart type ${chartType}`);\n }\n // Title can be separated into multiple xml elements (for styling and such), we only import the text\n const chartTitle = this.mapOnElements({ parent: rootChartElement, query: \"c:title a:t\" }, (textElement) => {\n return textElement.textContent || \"\";\n }).join(\"\");\n const barChartGrouping = this.extractChildAttr(rootChartElement, \"c:grouping\", \"val\", {\n default: \"clustered\",\n }).asString();\n return {\n title: chartTitle,\n type: CHART_TYPE_CONVERSION_MAP[chartType],\n dataSets: this.extractChartDatasets(this.querySelector(rootChartElement, `c:${chartType}`)),\n backgroundColor: this.extractChildAttr(rootChartElement, \"c:chartSpace > c:spPr a:srgbClr\", \"val\", {\n default: \"ffffff\",\n }).asString(),\n verticalAxisPosition: this.extractChildAttr(rootChartElement, \"c:valAx > c:axPos\", \"val\", {\n default: \"l\",\n }).asString() === \"r\"\n ? \"right\"\n : \"left\",\n legendPosition: DRAWING_LEGEND_POSITION_CONVERSION_MAP[this.extractChildAttr(rootChartElement, \"c:legendPos\", \"val\", {\n default: \"b\",\n }).asString()],\n stacked: barChartGrouping === \"stacked\",\n fontColor: \"000000\",\n };\n })[0];\n }\n extractChartDatasets(chartElement) {\n return this.mapOnElements({ parent: chartElement, query: \"c:ser\" }, (chartDataElement) => {\n return {\n label: this.extractChildTextContent(chartDataElement, \"c:cat c:f\"),\n range: this.extractChildTextContent(chartDataElement, \"c:val c:f\", { required: true }),\n };\n });\n }\n /**\n * The chart type in the XML isn't explicitly defined, but there is an XML element that define the\n * chart, and this element tag name tells us which type of chart it is. We just need to find this XML element.\n */\n getChartType(chartElement) {\n const plotAreaElement = this.querySelector(chartElement, \"c:plotArea\");\n if (!plotAreaElement) {\n throw new Error(\"Missing plot area in the chart definition.\");\n }\n for (let child of plotAreaElement.children) {\n const tag = removeNamespaces(child.tagName);\n if (XLSX_CHART_TYPES.some((chartType) => chartType === tag)) {\n return tag;\n }\n }\n throw new Error(\"Unknown chart type\");\n }\n }\n\n class XlsxFigureExtractor extends XlsxBaseExtractor {\n extractFigures() {\n return this.mapOnElements({ parent: this.rootFile.file.xml, query: \"xdr:wsDr\", children: true }, (figureElement) => {\n const anchorType = removeNamespaces(figureElement.tagName);\n if (anchorType !== \"twoCellAnchor\") {\n throw new Error(\"Only twoCellAnchor are supported for xlsx drawings.\");\n }\n const chartElement = this.querySelector(figureElement, \"c:chart\");\n if (!chartElement) {\n throw new Error(\"Only chart figures are currently supported.\");\n }\n return {\n anchors: [\n this.extractFigureAnchor(\"xdr:from\", figureElement),\n this.extractFigureAnchor(\"xdr:to\", figureElement),\n ],\n data: this.extractChart(chartElement),\n };\n });\n }\n extractFigureAnchor(anchorTag, figureElement) {\n const anchor = this.querySelector(figureElement, anchorTag);\n if (!anchor) {\n throw new Error(`Missing anchor element ${anchorTag}`);\n }\n return {\n col: Number(this.extractChildTextContent(anchor, \"xdr:col\", { required: true })),\n colOffset: Number(this.extractChildTextContent(anchor, \"xdr:colOff\", { required: true })),\n row: Number(this.extractChildTextContent(anchor, \"xdr:row\", { required: true })),\n rowOffset: Number(this.extractChildTextContent(anchor, \"xdr:rowOff\", { required: true })),\n };\n }\n extractChart(chartElement) {\n const chartId = this.extractAttr(chartElement, \"r:id\", { required: true }).asString();\n const chartFile = this.getTargetXmlFile(this.relationships[chartId]);\n const chartDefinition = new XlsxChartExtractor(chartFile, this.xlsxFileStructure, this.warningManager).extractChart();\n if (!chartDefinition) {\n throw new Error(\"Unable to extract chart definition\");\n }\n return chartDefinition;\n }\n }\n\n /**\n * We don't really support pivot tables, we'll just extract them as Tables.\n */\n class XlsxPivotExtractor extends XlsxBaseExtractor {\n getPivotTable() {\n return this.mapOnElements(\n // Use :root instead of \"pivotTableDefinition\" because others pivotTableDefinition elements are present inside the root\n // pivotTableDefinition elements.\n { query: \":root\", parent: this.rootFile.file.xml }, (pivotElement) => {\n return {\n displayName: this.extractAttr(pivotElement, \"name\", { required: true }).asString(),\n id: this.extractAttr(pivotElement, \"name\", { required: true }).asString(),\n ref: this.extractChildAttr(pivotElement, \"location\", \"ref\", {\n required: true,\n }).asString(),\n headerRowCount: this.extractChildAttr(pivotElement, \"location\", \"firstDataRow\", {\n default: 0,\n }).asNum(),\n totalsRowCount: 1,\n cols: [],\n style: {\n showFirstColumn: true,\n showRowStripes: true,\n },\n };\n })[0];\n }\n }\n\n class XlsxTableExtractor extends XlsxBaseExtractor {\n getTable() {\n return this.mapOnElements({ query: \"table\", parent: this.rootFile.file.xml }, (tableElement) => {\n var _a;\n return {\n displayName: this.extractAttr(tableElement, \"displayName\", {\n required: true,\n }).asString(),\n name: (_a = this.extractAttr(tableElement, \"name\")) === null || _a === void 0 ? void 0 : _a.asString(),\n id: this.extractAttr(tableElement, \"id\", { required: true }).asString(),\n ref: this.extractAttr(tableElement, \"ref\", { required: true }).asString(),\n headerRowCount: this.extractAttr(tableElement, \"headerRowCount\", {\n default: 1,\n }).asNum(),\n totalsRowCount: this.extractAttr(tableElement, \"totalsRowCount\", {\n default: 0,\n }).asNum(),\n cols: this.extractTableCols(tableElement),\n style: this.extractTableStyleInfo(tableElement),\n autoFilter: this.extractTableAutoFilter(tableElement),\n };\n })[0];\n }\n extractTableCols(tableElement) {\n return this.mapOnElements({ query: \"tableColumn\", parent: tableElement }, (tableColElement) => {\n return {\n id: this.extractAttr(tableColElement, \"id\", { required: true }).asString(),\n name: this.extractAttr(tableColElement, \"name\", { required: true }).asString(),\n colFormula: this.extractChildTextContent(tableColElement, \"calculatedColumnFormula\"),\n };\n });\n }\n extractTableStyleInfo(tableElement) {\n return this.mapOnElements({ query: \"tableStyleInfo\", parent: tableElement }, (tableStyleElement) => {\n var _a, _b, _c, _d, _e;\n return {\n name: (_a = this.extractAttr(tableStyleElement, \"name\")) === null || _a === void 0 ? void 0 : _a.asString(),\n showFirstColumn: (_b = this.extractAttr(tableStyleElement, \"showFirstColumn\")) === null || _b === void 0 ? void 0 : _b.asBool(),\n showLastColumn: (_c = this.extractAttr(tableStyleElement, \"showLastColumn\")) === null || _c === void 0 ? void 0 : _c.asBool(),\n showRowStripes: (_d = this.extractAttr(tableStyleElement, \"showRowStripes\")) === null || _d === void 0 ? void 0 : _d.asBool(),\n showColumnStripes: (_e = this.extractAttr(tableStyleElement, \"showColumnStripes\")) === null || _e === void 0 ? void 0 : _e.asBool(),\n };\n })[0];\n }\n extractTableAutoFilter(tableElement) {\n return this.mapOnElements({ query: \"autoFilter\", parent: tableElement }, (autoFilterElement) => {\n return {\n columns: this.extractFilterColumns(autoFilterElement),\n zone: this.extractAttr(autoFilterElement, \"ref\", { required: true }).asString(),\n };\n })[0];\n }\n extractFilterColumns(autoFilterElement) {\n return this.mapOnElements({ query: \"tableColumn\", parent: autoFilterElement }, (filterColumnElement) => {\n return {\n colId: this.extractAttr(autoFilterElement, \"colId\", { required: true }).asNum(),\n hiddenButton: this.extractAttr(autoFilterElement, \"hiddenButton\", {\n default: false,\n }).asBool(),\n filters: this.extractSimpleFilter(filterColumnElement),\n };\n });\n }\n extractSimpleFilter(filterColumnElement) {\n return this.mapOnElements({ query: \"filter\", parent: filterColumnElement }, (filterColumnElement) => {\n return {\n val: this.extractAttr(filterColumnElement, \"val\", { required: true }).asString(),\n };\n });\n }\n }\n\n class XlsxSheetExtractor extends XlsxBaseExtractor {\n constructor(sheetFile, xlsxStructure, warningManager, theme) {\n super(sheetFile, xlsxStructure, warningManager);\n this.theme = theme;\n }\n getSheet() {\n return this.mapOnElements({ query: \"worksheet\", parent: this.rootFile.file.xml }, (sheetElement) => {\n const sheetWorkbookInfo = this.getSheetWorkbookInfo();\n return {\n sheetName: this.extractSheetName(),\n sheetViews: this.extractSheetViews(sheetElement),\n sheetFormat: this.extractSheetFormat(sheetElement),\n cols: this.extractCols(sheetElement),\n rows: this.extractRows(sheetElement),\n sharedFormulas: this.extractSharedFormulas(sheetElement),\n merges: this.extractMerges(sheetElement),\n cfs: this.extractConditionalFormats(),\n figures: this.extractFigures(sheetElement),\n hyperlinks: this.extractHyperLinks(sheetElement),\n tables: [...this.extractTables(sheetElement), ...this.extractPivotTables()],\n isVisible: sheetWorkbookInfo.state === \"visible\" ? true : false,\n };\n })[0];\n }\n extractSheetViews(worksheet) {\n return this.mapOnElements({ parent: worksheet, query: \"sheetView\" }, (sheetViewElement) => {\n const paneElement = this.querySelector(sheetViewElement, \"pane\");\n return {\n tabSelected: this.extractAttr(sheetViewElement, \"tabSelected\", {\n default: false,\n }).asBool(),\n showFormulas: this.extractAttr(sheetViewElement, \"showFormulas\", {\n default: false,\n }).asBool(),\n showGridLines: this.extractAttr(sheetViewElement, \"showGridLines\", {\n default: true,\n }).asBool(),\n showRowColHeaders: this.extractAttr(sheetViewElement, \"showRowColHeaders\", {\n default: true,\n }).asBool(),\n pane: {\n xSplit: paneElement\n ? this.extractAttr(paneElement, \"xSplit\", { default: 0 }).asNum()\n : 0,\n ySplit: paneElement\n ? this.extractAttr(paneElement, \"ySplit\", { default: 0 }).asNum()\n : 0,\n },\n };\n });\n }\n extractSheetName() {\n const relativePath = getRelativePath(this.xlsxFileStructure.workbook.file.fileName, this.rootFile.file.fileName);\n const workbookRels = this.extractRelationships(this.xlsxFileStructure.workbook.rels);\n const relId = workbookRels.find((rel) => rel.target === relativePath).id;\n // Having a namespace in the attributes names mess with the querySelector, and the behavior is not the same\n // for every XML parser. So we'll search manually instead of using a querySelector to search for an attribute value.\n for (let sheetElement of this.querySelectorAll(this.xlsxFileStructure.workbook.file.xml, \"sheet\")) {\n if (sheetElement.attributes[\"r:id\"].value === relId) {\n return sheetElement.attributes[\"name\"].value;\n }\n }\n throw new Error(\"Missing sheet name\");\n }\n getSheetWorkbookInfo() {\n const relativePath = getRelativePath(this.xlsxFileStructure.workbook.file.fileName, this.rootFile.file.fileName);\n const workbookRels = this.extractRelationships(this.xlsxFileStructure.workbook.rels);\n const relId = workbookRels.find((rel) => rel.target === relativePath).id;\n const workbookSheets = this.mapOnElements({ parent: this.xlsxFileStructure.workbook.file.xml, query: \"sheet\" }, (sheetElement) => {\n return {\n relationshipId: this.extractAttr(sheetElement, \"r:id\", { required: true }).asString(),\n sheetId: this.extractAttr(sheetElement, \"sheetId\", { required: true }).asString(),\n sheetName: this.extractAttr(sheetElement, \"name\", { required: true }).asString(),\n state: this.extractAttr(sheetElement, \"state\", {\n default: \"visible\",\n }).asString(),\n };\n });\n const info = workbookSheets.find((info) => info.relationshipId === relId);\n if (!info) {\n throw new Error(\"Cannot find corresponding workbook sheet\");\n }\n return info;\n }\n extractConditionalFormats() {\n return new XlsxCfExtractor(this.rootFile, this.xlsxFileStructure, this.warningManager, this.theme).extractConditionalFormattings();\n }\n extractFigures(worksheet) {\n const figures = this.mapOnElements({ parent: worksheet, query: \"drawing\" }, (drawingElement) => {\n var _a;\n const drawingId = (_a = this.extractAttr(drawingElement, \"r:id\", { required: true })) === null || _a === void 0 ? void 0 : _a.asString();\n const drawingFile = this.getTargetXmlFile(this.relationships[drawingId]);\n const figures = new XlsxFigureExtractor(drawingFile, this.xlsxFileStructure, this.warningManager).extractFigures();\n return figures;\n })[0];\n return figures || [];\n }\n extractTables(worksheet) {\n return this.mapOnElements({ query: \"tablePart\", parent: worksheet }, (tablePartElement) => {\n var _a;\n const tableId = (_a = this.extractAttr(tablePartElement, \"r:id\", { required: true })) === null || _a === void 0 ? void 0 : _a.asString();\n const tableFile = this.getTargetXmlFile(this.relationships[tableId]);\n const tableExtractor = new XlsxTableExtractor(tableFile, this.xlsxFileStructure, this.warningManager);\n return tableExtractor.getTable();\n });\n }\n extractPivotTables() {\n try {\n return Object.values(this.relationships)\n .filter((relationship) => relationship.type.endsWith(\"pivotTable\"))\n .map((pivotRelationship) => {\n const pivotFile = this.getTargetXmlFile(pivotRelationship);\n const pivot = new XlsxPivotExtractor(pivotFile, this.xlsxFileStructure, this.warningManager).getPivotTable();\n return pivot;\n });\n }\n catch (e) {\n this.catchErrorOnElement(e);\n return [];\n }\n }\n extractMerges(worksheet) {\n return this.mapOnElements({ parent: worksheet, query: \"mergeCell\" }, (mergeElement) => {\n return this.extractAttr(mergeElement, \"ref\", { required: true }).asString();\n });\n }\n extractSheetFormat(worksheet) {\n const formatElement = this.querySelector(worksheet, \"sheetFormatPr\");\n if (!formatElement)\n return undefined;\n return {\n defaultColWidth: this.extractAttr(formatElement, \"defaultColWidth\", {\n default: EXCEL_DEFAULT_COL_WIDTH.toString(),\n }).asNum(),\n defaultRowHeight: this.extractAttr(formatElement, \"defaultRowHeight\", {\n default: EXCEL_DEFAULT_ROW_HEIGHT.toString(),\n }).asNum(),\n };\n }\n extractCols(worksheet) {\n return this.mapOnElements({ parent: worksheet, query: \"cols col\" }, (colElement) => {\n var _a, _b, _c, _d, _e, _f, _g;\n return {\n width: (_a = this.extractAttr(colElement, \"width\")) === null || _a === void 0 ? void 0 : _a.asNum(),\n customWidth: (_b = this.extractAttr(colElement, \"customWidth\")) === null || _b === void 0 ? void 0 : _b.asBool(),\n bestFit: (_c = this.extractAttr(colElement, \"bestFit\")) === null || _c === void 0 ? void 0 : _c.asBool(),\n hidden: (_d = this.extractAttr(colElement, \"hidden\")) === null || _d === void 0 ? void 0 : _d.asBool(),\n min: (_e = this.extractAttr(colElement, \"min\", { required: true })) === null || _e === void 0 ? void 0 : _e.asNum(),\n max: (_f = this.extractAttr(colElement, \"max\", { required: true })) === null || _f === void 0 ? void 0 : _f.asNum(),\n styleIndex: (_g = this.extractAttr(colElement, \"style\")) === null || _g === void 0 ? void 0 : _g.asNum(),\n };\n });\n }\n extractRows(worksheet) {\n return this.mapOnElements({ parent: worksheet, query: \"sheetData row\" }, (rowElement) => {\n var _a, _b, _c, _d, _e;\n return {\n index: (_a = this.extractAttr(rowElement, \"r\", { required: true })) === null || _a === void 0 ? void 0 : _a.asNum(),\n cells: this.extractCells(rowElement),\n height: (_b = this.extractAttr(rowElement, \"ht\")) === null || _b === void 0 ? void 0 : _b.asNum(),\n customHeight: (_c = this.extractAttr(rowElement, \"customHeight\")) === null || _c === void 0 ? void 0 : _c.asBool(),\n hidden: (_d = this.extractAttr(rowElement, \"hidden\")) === null || _d === void 0 ? void 0 : _d.asBool(),\n styleIndex: (_e = this.extractAttr(rowElement, \"s\")) === null || _e === void 0 ? void 0 : _e.asNum(),\n };\n });\n }\n extractCells(row) {\n return this.mapOnElements({ parent: row, query: \"c\" }, (cellElement) => {\n var _a, _b, _c;\n return {\n xc: (_a = this.extractAttr(cellElement, \"r\", { required: true })) === null || _a === void 0 ? void 0 : _a.asString(),\n styleIndex: (_b = this.extractAttr(cellElement, \"s\")) === null || _b === void 0 ? void 0 : _b.asNum(),\n type: CELL_TYPE_CONVERSION_MAP[(_c = this.extractAttr(cellElement, \"t\", { default: \"n\" })) === null || _c === void 0 ? void 0 : _c.asString()],\n value: this.extractChildTextContent(cellElement, \"v\"),\n formula: this.extractCellFormula(cellElement),\n };\n });\n }\n extractCellFormula(cellElement) {\n var _a, _b;\n const formulaElement = this.querySelector(cellElement, \"f\");\n if (!formulaElement)\n return undefined;\n return {\n content: this.extractTextContent(formulaElement),\n sharedIndex: (_a = this.extractAttr(formulaElement, \"si\")) === null || _a === void 0 ? void 0 : _a.asNum(),\n ref: (_b = this.extractAttr(formulaElement, \"ref\")) === null || _b === void 0 ? void 0 : _b.asString(),\n };\n }\n extractHyperLinks(worksheet) {\n return this.mapOnElements({ parent: worksheet, query: \"hyperlink\" }, (linkElement) => {\n var _a, _b, _c, _d;\n const relId = (_a = this.extractAttr(linkElement, \"r:id\")) === null || _a === void 0 ? void 0 : _a.asString();\n return {\n xc: (_b = this.extractAttr(linkElement, \"ref\", { required: true })) === null || _b === void 0 ? void 0 : _b.asString(),\n location: (_c = this.extractAttr(linkElement, \"location\")) === null || _c === void 0 ? void 0 : _c.asString(),\n display: (_d = this.extractAttr(linkElement, \"display\")) === null || _d === void 0 ? void 0 : _d.asString(),\n relTarget: relId ? this.relationships[relId].target : undefined,\n };\n });\n }\n extractSharedFormulas(worksheet) {\n const sfElements = this.querySelectorAll(worksheet, `f[si][ref]`);\n const sfMap = {};\n for (let sfElement of sfElements) {\n const index = this.extractAttr(sfElement, \"si\", { required: true }).asNum();\n const formula = this.extractTextContent(sfElement, { required: true });\n sfMap[index] = formula;\n }\n const sfs = [];\n for (let i = 0; i < Object.keys(sfMap).length; i++) {\n if (!sfMap[i]) {\n this.warningManager.addParsingWarning(`Missing shared formula ${i}, replacing it by empty formula`);\n sfs.push(\"\");\n }\n else {\n sfs.push(sfMap[i]);\n }\n }\n return sfs;\n }\n }\n\n class XlsxStyleExtractor extends XlsxBaseExtractor {\n constructor(xlsxStructure, warningManager, theme) {\n super(xlsxStructure.styles, xlsxStructure, warningManager);\n this.theme = theme;\n }\n getNumFormats() {\n return this.mapOnElements({ parent: this.rootFile.file.xml, query: \"numFmt\" }, (numFmtElement) => {\n return this.extractNumFormats(numFmtElement);\n });\n }\n extractNumFormats(numFmtElement) {\n return {\n id: this.extractAttr(numFmtElement, \"numFmtId\", {\n required: true,\n }).asNum(),\n format: this.extractAttr(numFmtElement, \"formatCode\", {\n required: true,\n default: \"\",\n }).asString(),\n };\n }\n getFonts() {\n return this.mapOnElements({ parent: this.rootFile.file.xml, query: \"font\" }, (font) => {\n return this.extractFont(font);\n });\n }\n extractFont(fontElement) {\n var _a, _b, _c, _d;\n const name = this.extractChildAttr(fontElement, \"name\", \"val\", {\n default: \"Arial\",\n }).asString();\n const size = this.extractChildAttr(fontElement, \"sz\", \"val\", {\n default: DEFAULT_FONT_SIZE.toString(),\n }).asNum();\n const color = this.extractColor(this.querySelector(fontElement, `color`), this.theme);\n // The behavior for these is kinda strange. The text is italic if there is either a \"italic\" tag with no \"val\"\n // attribute, or a tag with a \"val\" attribute = \"1\" (boolean).\n const italicElement = this.querySelector(fontElement, `i`) || undefined;\n const italic = italicElement && ((_a = italicElement.attributes[\"val\"]) === null || _a === void 0 ? void 0 : _a.value) !== \"0\";\n const boldElement = this.querySelector(fontElement, `b`) || undefined;\n const bold = boldElement && ((_b = boldElement.attributes[\"val\"]) === null || _b === void 0 ? void 0 : _b.value) !== \"0\";\n const strikeElement = this.querySelector(fontElement, `strike`) || undefined;\n const strike = strikeElement && ((_c = strikeElement.attributes[\"val\"]) === null || _c === void 0 ? void 0 : _c.value) !== \"0\";\n const underlineElement = this.querySelector(fontElement, `u`) || undefined;\n const underline = underlineElement && ((_d = underlineElement.attributes[\"val\"]) === null || _d === void 0 ? void 0 : _d.value) !== \"none\";\n return { name, size, color, italic, bold, underline, strike };\n }\n getFills() {\n return this.mapOnElements({ parent: this.rootFile.file.xml, query: \"fill\" }, (fillElement) => {\n return this.extractFill(fillElement);\n });\n }\n extractFill(fillElement) {\n var _a;\n // Fills are either patterns of gradients\n const fillChild = fillElement.children[0];\n if (fillChild.tagName === \"patternFill\") {\n return {\n patternType: (_a = fillChild.attributes[\"patternType\"]) === null || _a === void 0 ? void 0 : _a.value,\n bgColor: this.extractColor(this.querySelector(fillChild, \"bgColor\"), this.theme),\n fgColor: this.extractColor(this.querySelector(fillChild, \"fgColor\"), this.theme),\n };\n }\n else {\n // We don't support gradients. Take the second gradient color as fill color\n return {\n patternType: \"solid\",\n fgColor: this.extractColor(this.querySelectorAll(fillChild, \"color\")[1], this.theme),\n };\n }\n }\n getBorders() {\n return this.mapOnElements({ parent: this.rootFile.file.xml, query: \"border\" }, (borderElement) => {\n return this.extractBorder(borderElement);\n });\n }\n extractBorder(borderElement) {\n var _a, _b;\n const border = {\n left: this.extractSingleBorder(borderElement, \"left\", this.theme),\n right: this.extractSingleBorder(borderElement, \"right\", this.theme),\n top: this.extractSingleBorder(borderElement, \"top\", this.theme),\n bottom: this.extractSingleBorder(borderElement, \"bottom\", this.theme),\n diagonal: this.extractSingleBorder(borderElement, \"diagonal\", this.theme),\n };\n if (border.diagonal) {\n border.diagonalUp = (_a = this.extractAttr(borderElement, \"diagonalUp\")) === null || _a === void 0 ? void 0 : _a.asBool();\n border.diagonalDown = (_b = this.extractAttr(borderElement, \"diagonalDown\")) === null || _b === void 0 ? void 0 : _b.asBool();\n }\n return border;\n }\n extractSingleBorder(borderElement, direction, theme) {\n const directionElement = this.querySelector(borderElement, direction);\n if (!directionElement || !directionElement.attributes[\"style\"])\n return undefined;\n return {\n style: this.extractAttr(directionElement, \"style\", {\n required: true,\n default: \"thin\",\n }).asString(),\n color: this.extractColor(directionElement.children[0], theme, \"000000\"),\n };\n }\n extractAlignment(alignmentElement) {\n var _a, _b, _c, _d, _e, _f, _g;\n return {\n horizontal: this.extractAttr(alignmentElement, \"horizontal\", {\n default: \"general\",\n }).asString(),\n vertical: this.extractAttr(alignmentElement, \"vertical\", {\n default: \"center\",\n }).asString(),\n textRotation: (_a = this.extractAttr(alignmentElement, \"textRotation\")) === null || _a === void 0 ? void 0 : _a.asNum(),\n wrapText: (_b = this.extractAttr(alignmentElement, \"wrapText\")) === null || _b === void 0 ? void 0 : _b.asBool(),\n indent: (_c = this.extractAttr(alignmentElement, \"indent\")) === null || _c === void 0 ? void 0 : _c.asNum(),\n relativeIndent: (_d = this.extractAttr(alignmentElement, \"relativeIndent\")) === null || _d === void 0 ? void 0 : _d.asNum(),\n justifyLastLine: (_e = this.extractAttr(alignmentElement, \"justifyLastLine\")) === null || _e === void 0 ? void 0 : _e.asBool(),\n shrinkToFit: (_f = this.extractAttr(alignmentElement, \"shrinkToFit\")) === null || _f === void 0 ? void 0 : _f.asBool(),\n readingOrder: (_g = this.extractAttr(alignmentElement, \"readingOrder\")) === null || _g === void 0 ? void 0 : _g.asNum(),\n };\n }\n getDxfs() {\n return this.mapOnElements({ query: \"dxf\", parent: this.rootFile.file.xml }, (dxfElement) => {\n const fontElement = this.querySelector(dxfElement, \"font\");\n const fillElement = this.querySelector(dxfElement, \"fill\");\n const borderElement = this.querySelector(dxfElement, \"border\");\n const numFmtElement = this.querySelector(dxfElement, \"numFmt\");\n const alignmentElement = this.querySelector(dxfElement, \"alignment\");\n return {\n font: fontElement ? this.extractFont(fontElement) : undefined,\n fill: fillElement ? this.extractFill(fillElement) : undefined,\n numFmt: numFmtElement ? this.extractNumFormats(numFmtElement) : undefined,\n alignment: alignmentElement ? this.extractAlignment(alignmentElement) : undefined,\n border: borderElement ? this.extractBorder(borderElement) : undefined,\n };\n });\n }\n getStyles() {\n return this.mapOnElements({ query: \"cellXfs xf\", parent: this.rootFile.file.xml }, (styleElement) => {\n const alignmentElement = this.querySelector(styleElement, \"alignment\");\n return {\n fontId: this.extractAttr(styleElement, \"fontId\", {\n required: true,\n default: 0,\n }).asNum(),\n fillId: this.extractAttr(styleElement, \"fillId\", {\n required: true,\n default: 0,\n }).asNum(),\n borderId: this.extractAttr(styleElement, \"borderId\", {\n required: true,\n default: 0,\n }).asNum(),\n numFmtId: this.extractAttr(styleElement, \"numFmtId\", {\n required: true,\n default: 0,\n }).asNum(),\n alignment: alignmentElement ? this.extractAlignment(alignmentElement) : undefined,\n };\n });\n }\n }\n\n class XlsxExternalBookExtractor extends XlsxBaseExtractor {\n getExternalBook() {\n return this.mapOnElements({ parent: this.rootFile.file.xml, query: \"externalBook\" }, (bookElement) => {\n return {\n rId: this.extractAttr(bookElement, \"r:id\", { required: true }).asString(),\n sheetNames: this.mapOnElements({ parent: bookElement, query: \"sheetName\" }, (sheetNameElement) => {\n return this.extractAttr(sheetNameElement, \"val\", { required: true }).asString();\n }),\n datasets: this.extractExternalSheetData(bookElement),\n };\n })[0];\n }\n extractExternalSheetData(externalBookElement) {\n return this.mapOnElements({ parent: externalBookElement, query: \"sheetData\" }, (sheetDataElement) => {\n const cellsData = this.mapOnElements({ parent: sheetDataElement, query: \"cell\" }, (cellElement) => {\n return {\n xc: this.extractAttr(cellElement, \"r\", { required: true }).asString(),\n value: this.extractChildTextContent(cellElement, \"v\", { required: true }),\n };\n });\n const dataMap = {};\n for (let cell of cellsData) {\n dataMap[cell.xc] = cell.value;\n }\n return {\n sheetId: this.extractAttr(sheetDataElement, \"sheetId\", { required: true }).asNum(),\n data: dataMap,\n };\n });\n }\n }\n\n /**\n * Return all the xmls converted to XLSXImportFile corresponding to the given content type.\n */\n function getXLSXFilesOfType(contentType, xmls) {\n const paths = getPathsOfContent(contentType, xmls);\n return getXlsxFile(paths, xmls);\n }\n /**\n * From an array of file path, return the equivalents XLSXFiles. An XLSX File is composed of an XML,\n * and optionally of a relationships XML.\n */\n function getXlsxFile(files, xmls) {\n const ret = [];\n for (let file of files) {\n const rels = getRelationFile(file, xmls);\n ret.push({\n file: { fileName: file, xml: xmls[file] },\n rels: rels ? { fileName: rels, xml: xmls[rels] } : undefined,\n });\n }\n return ret;\n }\n /**\n * Return all the path of the files in a XLSX directory that have content of the given type.\n */\n function getPathsOfContent(contentType, xmls) {\n const xml = xmls[CONTENT_TYPES_FILE];\n const sheetItems = xml.querySelectorAll(`Override[ContentType=\"${contentType}\"]`);\n const paths = [];\n for (let item of sheetItems) {\n const file = item === null || item === void 0 ? void 0 : item.attributes[\"PartName\"].value;\n paths.push(file.substring(1)); // Remove the heading \"/\"\n }\n return paths;\n }\n /**\n * Get the corresponding relationship file for a given xml file in a XLSX directory.\n */\n function getRelationFile(file, xmls) {\n if (file === CONTENT_TYPES_FILE) {\n return \"_rels/.rels\";\n }\n let relsFile = \"\";\n const pathParts = file.split(\"/\");\n for (let i = 0; i < pathParts.length - 1; i++) {\n relsFile += pathParts[i] + \"/\";\n }\n relsFile += \"_rels/\";\n relsFile += pathParts[pathParts.length - 1] + \".rels\";\n if (!xmls[relsFile]) {\n relsFile = undefined;\n }\n return relsFile;\n }\n\n const EXCEL_IMPORT_VERSION = 12;\n class XlsxReader {\n constructor(files) {\n this.warningManager = new XLSXImportWarningManager();\n this.xmls = {};\n for (let key of Object.keys(files)) {\n // Random files can be in xlsx (like a bin file for printer settings)\n if (key.endsWith(\".xml\") || key.endsWith(\".rels\")) {\n this.xmls[key] = parseXML(new XMLString(files[key]));\n }\n }\n }\n convertXlsx() {\n const xlsxData = this.getXlsxData();\n const convertedData = this.convertImportedData(xlsxData);\n return convertedData;\n }\n // ---------------------------------------------------------------------------\n // Parsing XMLs\n // ---------------------------------------------------------------------------\n getXlsxData() {\n const xlsxFileStructure = this.buildXlsxFileStructure();\n const theme = xlsxFileStructure.theme\n ? new XlsxMiscExtractor(xlsxFileStructure.theme, xlsxFileStructure, this.warningManager).getTheme()\n : undefined;\n const sharedStrings = xlsxFileStructure.sharedStrings\n ? new XlsxMiscExtractor(xlsxFileStructure.sharedStrings, xlsxFileStructure, this.warningManager).getSharedStrings()\n : [];\n // Sort sheets by file name : the sheets will always be named sheet1.xml, sheet2.xml, ... in order\n const sheets = xlsxFileStructure.sheets\n .sort((a, b) => a.file.fileName.localeCompare(b.file.fileName, undefined, { numeric: true }))\n .map((sheetFile) => {\n return new XlsxSheetExtractor(sheetFile, xlsxFileStructure, this.warningManager, theme).getSheet();\n });\n const externalBooks = xlsxFileStructure.externalLinks.map((externalLinkFile) => {\n return new XlsxExternalBookExtractor(externalLinkFile, xlsxFileStructure, this.warningManager).getExternalBook();\n });\n const styleExtractor = new XlsxStyleExtractor(xlsxFileStructure, this.warningManager, theme);\n return {\n fonts: styleExtractor.getFonts(),\n fills: styleExtractor.getFills(),\n borders: styleExtractor.getBorders(),\n dxfs: styleExtractor.getDxfs(),\n numFmts: styleExtractor.getNumFormats(),\n styles: styleExtractor.getStyles(),\n sheets: sheets,\n sharedStrings,\n externalBooks,\n };\n }\n buildXlsxFileStructure() {\n const xlsxFileStructure = {\n sheets: getXLSXFilesOfType(CONTENT_TYPES.sheet, this.xmls),\n workbook: getXLSXFilesOfType(CONTENT_TYPES.workbook, this.xmls)[0],\n styles: getXLSXFilesOfType(CONTENT_TYPES.styles, this.xmls)[0],\n sharedStrings: getXLSXFilesOfType(CONTENT_TYPES.sharedStrings, this.xmls)[0],\n theme: getXLSXFilesOfType(CONTENT_TYPES.themes, this.xmls)[0],\n charts: getXLSXFilesOfType(CONTENT_TYPES.chart, this.xmls),\n figures: getXLSXFilesOfType(CONTENT_TYPES.drawing, this.xmls),\n tables: getXLSXFilesOfType(CONTENT_TYPES.table, this.xmls),\n pivots: getXLSXFilesOfType(CONTENT_TYPES.pivot, this.xmls),\n externalLinks: getXLSXFilesOfType(CONTENT_TYPES.externalLink, this.xmls),\n };\n if (!xlsxFileStructure.workbook.rels) {\n throw Error(_lt(\"Cannot find workbook relations file\"));\n }\n return xlsxFileStructure;\n }\n // ---------------------------------------------------------------------------\n // Conversion\n // ---------------------------------------------------------------------------\n convertImportedData(data) {\n const convertedData = {\n version: EXCEL_IMPORT_VERSION,\n sheets: convertSheets(data, this.warningManager),\n styles: convertStyles(data, this.warningManager),\n formats: convertFormats(data, this.warningManager),\n borders: convertBorders(data, this.warningManager),\n entities: {},\n revisionId: DEFAULT_REVISION_ID,\n };\n convertTables(convertedData, data);\n // Remove falsy attributes in styles. Not mandatory, but make objects more readable when debugging\n Object.keys(data.styles).map((key) => {\n data.styles[key] = removeFalsyAttributes(data.styles[key]);\n });\n return convertedData;\n }\n }\n\n /**\n * parses a formula (as a string) into the same formula,\n * but with the references to other cells extracted\n *\n * =sum(a3:b1) + c3 --> =sum(|0|) + |1|\n *\n * @param formula\n */\n function normalizeV9(formula) {\n const tokens = rangeTokenize(formula);\n let dependencies = [];\n let noRefFormula = \"\".concat(...tokens.map((token) => {\n if (token.type === \"REFERENCE\" && cellReference.test(token.value)) {\n const value = token.value.trim();\n if (!dependencies.includes(value)) {\n dependencies.push(value);\n }\n return `${FORMULA_REF_IDENTIFIER}${dependencies.indexOf(value)}${FORMULA_REF_IDENTIFIER}`;\n }\n else {\n return token.value;\n }\n }));\n return { text: noRefFormula, dependencies };\n }\n\n /**\n * This is the current state version number. It should be incremented each time\n * a breaking change is made in the way the state is handled, and an upgrade\n * function should be defined\n */\n const CURRENT_VERSION = 12;\n const INITIAL_SHEET_ID = \"Sheet1\";\n /**\n * This function tries to load anything that could look like a valid\n * workbookData object. It applies any migrations, if needed, and return a\n * current, complete workbookData object.\n *\n * It also ensures that there is at least one sheet.\n */\n function load(data, verboseImport) {\n if (!data) {\n return createEmptyWorkbookData();\n }\n if (data[\"[Content_Types].xml\"]) {\n const reader = new XlsxReader(data);\n data = reader.convertXlsx();\n if (verboseImport) {\n for (let parsingError of reader.warningManager.warnings.sort()) {\n console.warn(parsingError);\n }\n }\n }\n data = JSON.parse(JSON.stringify(data));\n // apply migrations, if needed\n if (\"version\" in data) {\n if (data.version < CURRENT_VERSION) {\n data = migrate(data);\n }\n }\n data = repairData(data);\n return data;\n }\n function migrate(data) {\n const index = MIGRATIONS.findIndex((m) => m.from === data.version);\n for (let i = index; i < MIGRATIONS.length; i++) {\n data = MIGRATIONS[i].applyMigration(data);\n }\n return data;\n }\n const MIGRATIONS = [\n {\n description: \"add the `activeSheet` field on data\",\n from: 1,\n to: 2,\n applyMigration(data) {\n if (data.sheets && data.sheets[0]) {\n data.activeSheet = data.sheets[0].name;\n }\n return data;\n },\n },\n {\n description: \"add an id field in each sheet\",\n from: 2,\n to: 3,\n applyMigration(data) {\n if (data.sheets && data.sheets.length) {\n for (let sheet of data.sheets) {\n sheet.id = sheet.id || sheet.name;\n }\n }\n return data;\n },\n },\n {\n description: \"activeSheet is now an id, not the name of a sheet\",\n from: 3,\n to: 4,\n applyMigration(data) {\n if (data.sheets && data.activeSheet) {\n const activeSheet = data.sheets.find((s) => s.name === data.activeSheet);\n data.activeSheet = activeSheet.id;\n }\n return data;\n },\n },\n {\n description: \"add figures object in each sheets\",\n from: 4,\n to: 5,\n applyMigration(data) {\n for (let sheet of data.sheets || []) {\n sheet.figures = sheet.figures || [];\n }\n return data;\n },\n },\n {\n description: \"normalize the content of the cell if it is a formula to avoid parsing all the formula that vary only by the cells they use\",\n from: 5,\n to: 6,\n applyMigration(data) {\n for (let sheet of data.sheets || []) {\n for (let xc in sheet.cells || []) {\n const cell = sheet.cells[xc];\n if (cell.content && cell.content.startsWith(\"=\")) {\n cell.formula = normalizeV9(cell.content);\n }\n }\n }\n return data;\n },\n },\n {\n description: \"transform chart data structure\",\n from: 6,\n to: 7,\n applyMigration(data) {\n for (let sheet of data.sheets || []) {\n for (let f in sheet.figures || []) {\n const { dataSets, ...newData } = sheet.figures[f].data;\n const newDataSets = [];\n for (let ds of dataSets) {\n if (ds.labelCell) {\n const dataRange = toZone(ds.dataRange);\n const newRange = ds.labelCell + \":\" + toXC(dataRange.right, dataRange.bottom);\n newDataSets.push(newRange);\n }\n else {\n newDataSets.push(ds.dataRange);\n }\n }\n newData.dataSetsHaveTitle = Boolean(dataSets[0].labelCell);\n newData.dataSets = newDataSets;\n sheet.figures[f].data = newData;\n }\n }\n return data;\n },\n },\n {\n description: \"remove single quotes in sheet names\",\n from: 7,\n to: 8,\n applyMigration(data) {\n var _a;\n const namesTaken = [];\n const globalForbiddenInExcel = new RegExp(FORBIDDEN_IN_EXCEL_REGEX, \"g\");\n for (let sheet of data.sheets || []) {\n if (!sheet.name) {\n continue;\n }\n const oldName = sheet.name;\n const escapedName = oldName.replace(globalForbiddenInExcel, \"_\");\n let i = 1;\n let newName = escapedName;\n while (namesTaken.includes(newName)) {\n newName = `${escapedName}${i}`;\n i++;\n }\n sheet.name = newName;\n namesTaken.push(newName);\n const replaceName = (str) => {\n if (str === undefined) {\n return str;\n }\n // replaceAll is only available in next Typescript version\n let newString = str.replace(oldName, newName);\n let currentString = str;\n while (currentString !== newString) {\n currentString = newString;\n newString = currentString.replace(oldName, newName);\n }\n return currentString;\n };\n //cells\n for (let xc in sheet.cells) {\n const cell = sheet.cells[xc];\n if (cell.formula) {\n cell.formula.dependencies = cell.formula.dependencies.map(replaceName);\n }\n }\n //charts\n for (let figure of sheet.figures || []) {\n if (figure.type === \"chart\") {\n const dataSets = figure.data.dataSets.map(replaceName);\n const labelRange = replaceName(figure.data.labelRange);\n figure.data = { ...figure.data, dataSets, labelRange };\n }\n }\n //ConditionalFormats\n for (let cf of sheet.conditionalFormats || []) {\n cf.ranges = cf.ranges.map(replaceName);\n for (const thresholdName of [\n \"minimum\",\n \"maximum\",\n \"midpoint\",\n \"upperInflectionPoint\",\n \"lowerInflectionPoint\",\n ]) {\n if (((_a = cf.rule[thresholdName]) === null || _a === void 0 ? void 0 : _a.type) === \"formula\") {\n cf.rule[thresholdName].value = replaceName(cf.rule[thresholdName].value);\n }\n }\n }\n }\n return data;\n },\n },\n {\n description: \"transform chart data structure with design attributes\",\n from: 8,\n to: 9,\n applyMigration(data) {\n for (const sheet of data.sheets || []) {\n for (const chart of sheet.figures || []) {\n chart.data.background = BACKGROUND_CHART_COLOR;\n chart.data.verticalAxisPosition = \"left\";\n chart.data.legendPosition = \"top\";\n chart.data.stacked = false;\n }\n }\n return data;\n },\n },\n {\n description: \"de-normalize formula to reduce exported json size (~30%)\",\n from: 9,\n to: 10,\n applyMigration(data) {\n for (let sheet of data.sheets || []) {\n for (let xc in sheet.cells || []) {\n const cell = sheet.cells[xc];\n if (cell.formula) {\n let { text, dependencies } = cell.formula;\n for (let [index, d] of Object.entries(dependencies)) {\n const stringPosition = `\\\\${FORMULA_REF_IDENTIFIER}${index}\\\\${FORMULA_REF_IDENTIFIER}`;\n text = text.replace(new RegExp(stringPosition, \"g\"), d);\n }\n cell.content = text;\n delete cell.formula;\n }\n }\n }\n return data;\n },\n },\n {\n description: \"normalize the formats of the cells\",\n from: 10,\n to: 11,\n applyMigration(data) {\n const formats = {};\n for (let sheet of data.sheets || []) {\n for (let xc in sheet.cells || []) {\n const cell = sheet.cells[xc];\n if (cell.format) {\n cell.format = getItemId(cell.format, formats);\n }\n }\n }\n data.formats = formats;\n return data;\n },\n },\n {\n description: \"Add isVisible to sheets\",\n from: 11,\n to: 12,\n applyMigration(data) {\n for (let sheet of data.sheets || []) {\n sheet.isVisible = true;\n }\n return data;\n },\n },\n ];\n /**\n * This function is used to repair faulty data independently of the migration.\n */\n function repairData(data) {\n data = forceUnicityOfFigure(data);\n data = setDefaults(data);\n return data;\n }\n /**\n * Force the unicity of figure ids accross sheets\n */\n function forceUnicityOfFigure(data) {\n if (data.uniqueFigureIds) {\n return data;\n }\n const figureIds = new Set();\n const uuidGenerator = new UuidGenerator();\n for (const sheet of data.sheets || []) {\n for (const figure of sheet.figures || []) {\n if (figureIds.has(figure.id)) {\n figure.id += uuidGenerator.uuidv4();\n }\n figureIds.add(figure.id);\n }\n }\n data.uniqueFigureIds = true;\n return data;\n }\n /**\n * sanity check: try to fix missing fields/corrupted state by providing\n * sensible default values\n */\n function setDefaults(data) {\n data = Object.assign(createEmptyWorkbookData(), data, { version: CURRENT_VERSION });\n data.sheets = data.sheets\n ? data.sheets.map((s, i) => Object.assign(createEmptySheet(`Sheet${i + 1}`, `Sheet${i + 1}`), s))\n : [];\n if (data.sheets.length === 0) {\n data.sheets.push(createEmptySheet(INITIAL_SHEET_ID, \"Sheet1\"));\n }\n return data;\n }\n /**\n * The goal of this function is to repair corrupted/wrong initial messages caused by\n * a bug.\n * The bug should obviously be fixed, but it's too late for existing spreadsheet.\n */\n function repairInitialMessages(data, initialMessages) {\n initialMessages = fixTranslatedSheetIds(data, initialMessages);\n initialMessages = dropCommands(initialMessages, \"SORT_CELLS\");\n initialMessages = dropCommands(initialMessages, \"SET_DECIMAL\");\n initialMessages = fixChartDefinitions(data, initialMessages);\n return initialMessages;\n }\n /**\n * When the workbook data is originally empty, a new one is generated on-the-fly.\n * A bug caused the sheet id to be non-deterministic. The sheet id was propagated in\n * commands.\n * This function repairs initial commands with a wrong sheetId.\n */\n function fixTranslatedSheetIds(data, initialMessages) {\n // the fix is only needed when the workbook is generated on-the-fly\n if (Object.keys(data).length !== 0) {\n return initialMessages;\n }\n const sheetIds = [];\n const messages = [];\n const fixSheetId = (cmd) => {\n if (cmd.type === \"CREATE_SHEET\") {\n sheetIds.push(cmd.sheetId);\n }\n else if (\"sheetId\" in cmd && !sheetIds.includes(cmd.sheetId)) {\n return { ...cmd, sheetId: INITIAL_SHEET_ID };\n }\n return cmd;\n };\n for (const message of initialMessages) {\n if (message.type === \"REMOTE_REVISION\") {\n messages.push({\n ...message,\n commands: message.commands.map(fixSheetId),\n });\n }\n else {\n messages.push(message);\n }\n }\n return messages;\n }\n function dropCommands(initialMessages, commandType) {\n const messages = [];\n for (const message of initialMessages) {\n if (message.type === \"REMOTE_REVISION\") {\n messages.push({\n ...message,\n commands: message.commands.filter((command) => command.type !== commandType),\n });\n }\n else {\n messages.push(message);\n }\n }\n return messages;\n }\n function fixChartDefinitions(data, initialMessages) {\n var _a;\n const messages = [];\n const map = {};\n for (const sheet of data.sheets || []) {\n (_a = sheet.figures) === null || _a === void 0 ? void 0 : _a.forEach((figure) => {\n if (figure.tag === \"chart\") {\n // chart definition\n map[figure.id] = figure.data;\n }\n });\n }\n for (const message of initialMessages) {\n if (message.type === \"REMOTE_REVISION\") {\n const commands = [];\n for (const cmd of message.commands) {\n let command = cmd;\n switch (cmd.type) {\n case \"CREATE_CHART\":\n map[cmd.id] = cmd.definition;\n break;\n case \"UPDATE_CHART\":\n if (!map[cmd.id]) {\n /** the chart does not exist on the map, it might have been created after a duplicate sheet.\n * We don't have access to the definition, so we skip the command.\n */\n console.log(`Fix chart definition: chart with id ${cmd.id} not found.`);\n continue;\n }\n const definition = map[cmd.id];\n const newDefinition = { ...definition, ...cmd.definition };\n command = { ...cmd, definition: newDefinition };\n map[cmd.id] = newDefinition;\n break;\n }\n commands.push(command);\n }\n messages.push({\n ...message,\n commands,\n });\n }\n else {\n messages.push(message);\n }\n }\n return messages;\n }\n // -----------------------------------------------------------------------------\n // Helpers\n // -----------------------------------------------------------------------------\n function createEmptySheet(sheetId, name) {\n return {\n id: sheetId,\n name,\n colNumber: 26,\n rowNumber: 100,\n cells: {},\n cols: {},\n rows: {},\n merges: [],\n conditionalFormats: [],\n figures: [],\n filterTables: [],\n isVisible: true,\n };\n }\n function createEmptyWorkbookData(sheetName = \"Sheet1\") {\n const data = {\n version: CURRENT_VERSION,\n sheets: [createEmptySheet(INITIAL_SHEET_ID, sheetName)],\n entities: {},\n styles: {},\n formats: {},\n borders: {},\n revisionId: DEFAULT_REVISION_ID,\n uniqueFigureIds: true,\n };\n return data;\n }\n function createEmptyExcelSheet(sheetId, name) {\n return {\n ...createEmptySheet(sheetId, name),\n charts: [],\n };\n }\n function createEmptyExcelWorkbookData() {\n return {\n ...createEmptyWorkbookData(),\n sheets: [createEmptyExcelSheet(INITIAL_SHEET_ID, \"Sheet1\")],\n };\n }\n\n /**\n * Core plugins handle spreadsheet data.\n * They are responsible to import, export and maintain the spreadsheet\n * persisted state.\n * They should not be concerned about UI parts or transient state.\n */\n class CorePlugin extends BasePlugin {\n constructor({ getters, stateObserver, range, dispatch, uuidGenerator }) {\n super(stateObserver, dispatch);\n this.range = range;\n range.addRangeProvider(this.adaptRanges.bind(this));\n this.getters = getters;\n this.uuidGenerator = uuidGenerator;\n }\n // ---------------------------------------------------------------------------\n // Import/Export\n // ---------------------------------------------------------------------------\n import(data) { }\n export(data) { }\n /**\n * This method can be implemented in any plugin, to loop over the plugin's data structure and adapt the plugin's ranges.\n * To adapt them, the implementation of the function must have a perfect knowledge of the data structure, thus\n * implementing the loops over it makes sense in the plugin itself.\n * When calling the method applyChange, the range will be adapted if necessary, then a copy will be returned along with\n * the type of change that occurred.\n *\n * @param applyChange a function that, when called, will adapt the range according to the change on the grid\n * @param sheetId an optional sheetId to adapt either range of that sheet specifically, or ranges pointing to that sheet\n */\n adaptRanges(applyChange, sheetId) { }\n /**\n * Implement this method to clean unused external resources, such as images\n * stored on a server which have been deleted.\n */\n garbageCollectExternalResources() { }\n }\n\n /**\n * Formatting plugin.\n *\n * This plugin manages all things related to a cell look:\n * - borders\n */\n class BordersPlugin extends CorePlugin {\n constructor() {\n super(...arguments);\n this.borders = {};\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n handle(cmd) {\n switch (cmd.type) {\n case \"ADD_MERGE\":\n for (const zone of cmd.target) {\n this.addBordersToMerge(cmd.sheetId, zone);\n }\n break;\n case \"DUPLICATE_SHEET\":\n const borders = this.borders[cmd.sheetId];\n if (borders) {\n // borders is a sparse 2D array.\n // map and slice preserve empty values and do not set `undefined` instead\n const bordersCopy = borders\n .slice()\n .map((col) => col === null || col === void 0 ? void 0 : col.slice().map((border) => ({ ...border })));\n this.history.update(\"borders\", cmd.sheetIdTo, bordersCopy);\n }\n break;\n case \"DELETE_SHEET\":\n const allBorders = { ...this.borders };\n delete allBorders[cmd.sheetId];\n this.history.update(\"borders\", allBorders);\n break;\n case \"SET_BORDER\":\n this.setBorder(cmd.sheetId, cmd.col, cmd.row, cmd.border);\n break;\n case \"SET_FORMATTING\":\n if (cmd.border) {\n const target = cmd.target.map((zone) => this.getters.expandZone(cmd.sheetId, zone));\n this.setBorders(cmd.sheetId, target, cmd.border);\n }\n break;\n case \"CLEAR_FORMATTING\":\n this.clearBorders(cmd.sheetId, cmd.target);\n break;\n case \"REMOVE_COLUMNS_ROWS\":\n for (let el of cmd.elements) {\n if (cmd.dimension === \"COL\") {\n this.shiftBordersHorizontally(cmd.sheetId, el + 1, -1);\n }\n else {\n this.shiftBordersVertically(cmd.sheetId, el + 1, -1);\n }\n }\n break;\n case \"ADD_COLUMNS_ROWS\":\n if (cmd.dimension === \"COL\") {\n this.handleAddColumns(cmd);\n }\n else {\n this.handleAddRows(cmd);\n }\n break;\n }\n }\n /**\n * Move borders according to the inserted columns.\n * Ensure borders continuity.\n */\n handleAddColumns(cmd) {\n // The new columns have already been inserted in the sheet at this point.\n let colLeftOfInsertion;\n let colRightOfInsertion;\n if (cmd.position === \"before\") {\n this.shiftBordersHorizontally(cmd.sheetId, cmd.base, cmd.quantity, {\n moveFirstLeftBorder: true,\n });\n colLeftOfInsertion = cmd.base - 1;\n colRightOfInsertion = cmd.base + cmd.quantity;\n }\n else {\n this.shiftBordersHorizontally(cmd.sheetId, cmd.base + 1, cmd.quantity, {\n moveFirstLeftBorder: false,\n });\n colLeftOfInsertion = cmd.base;\n colRightOfInsertion = cmd.base + cmd.quantity + 1;\n }\n this.ensureColumnBorderContinuity(cmd.sheetId, colLeftOfInsertion, colRightOfInsertion);\n }\n /**\n * Move borders according to the inserted rows.\n * Ensure borders continuity.\n */\n handleAddRows(cmd) {\n // The new rows have already been inserted at this point.\n let rowAboveInsertion;\n let rowBelowInsertion;\n if (cmd.position === \"before\") {\n this.shiftBordersVertically(cmd.sheetId, cmd.base, cmd.quantity, {\n moveFirstTopBorder: true,\n });\n rowAboveInsertion = cmd.base - 1;\n rowBelowInsertion = cmd.base + cmd.quantity;\n }\n else {\n this.shiftBordersVertically(cmd.sheetId, cmd.base + 1, cmd.quantity, {\n moveFirstTopBorder: false,\n });\n rowAboveInsertion = cmd.base;\n rowBelowInsertion = cmd.base + cmd.quantity + 1;\n }\n this.ensureRowBorderContinuity(cmd.sheetId, rowAboveInsertion, rowBelowInsertion);\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n getCellBorder({ sheetId, col, row }) {\n var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;\n const border = {\n top: (_c = (_b = (_a = this.borders[sheetId]) === null || _a === void 0 ? void 0 : _a[col]) === null || _b === void 0 ? void 0 : _b[row]) === null || _c === void 0 ? void 0 : _c.horizontal,\n bottom: (_f = (_e = (_d = this.borders[sheetId]) === null || _d === void 0 ? void 0 : _d[col]) === null || _e === void 0 ? void 0 : _e[row + 1]) === null || _f === void 0 ? void 0 : _f.horizontal,\n left: (_j = (_h = (_g = this.borders[sheetId]) === null || _g === void 0 ? void 0 : _g[col]) === null || _h === void 0 ? void 0 : _h[row]) === null || _j === void 0 ? void 0 : _j.vertical,\n right: (_m = (_l = (_k = this.borders[sheetId]) === null || _k === void 0 ? void 0 : _k[col + 1]) === null || _l === void 0 ? void 0 : _l[row]) === null || _m === void 0 ? void 0 : _m.vertical,\n };\n if (!border.bottom && !border.left && !border.right && !border.top) {\n return null;\n }\n return border;\n }\n // ---------------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------------\n /**\n * Ensure border continuity between two columns.\n * If the two columns have the same borders (at each row respectively),\n * the same borders are applied to each cell in between.\n */\n ensureColumnBorderContinuity(sheetId, leftColumn, rightColumn) {\n const targetCols = range(leftColumn + 1, rightColumn);\n for (let row = 0; row < this.getters.getNumberRows(sheetId); row++) {\n const leftBorder = this.getCellBorder({ sheetId, col: leftColumn, row });\n const rightBorder = this.getCellBorder({ sheetId, col: rightColumn, row });\n if (leftBorder && rightBorder) {\n const commonSides = this.getCommonSides(leftBorder, rightBorder);\n for (let col of targetCols) {\n this.addBorder(sheetId, col, row, commonSides);\n }\n }\n }\n }\n /**\n * Ensure border continuity between two rows.\n * If the two rows have the same borders (at each column respectively),\n * the same borders are applied to each cell in between.\n */\n ensureRowBorderContinuity(sheetId, topRow, bottomRow) {\n const targetRows = range(topRow + 1, bottomRow);\n for (let col = 0; col < this.getters.getNumberCols(sheetId); col++) {\n const aboveBorder = this.getCellBorder({ sheetId, col, row: topRow });\n const belowBorder = this.getCellBorder({ sheetId, col, row: bottomRow });\n if (aboveBorder && belowBorder) {\n const commonSides = this.getCommonSides(aboveBorder, belowBorder);\n for (let row of targetRows) {\n this.addBorder(sheetId, col, row, commonSides);\n }\n }\n }\n }\n /**\n * From two borders, return a new border with sides defined in both borders.\n * i.e. the intersection of two borders.\n */\n getCommonSides(border1, border2) {\n const commonBorder = {};\n for (let side of [\"top\", \"bottom\", \"left\", \"right\"]) {\n if (border1[side] && border1[side] === border2[side]) {\n commonBorder[side] = border1[side];\n }\n }\n return commonBorder;\n }\n /**\n * Get all the columns which contains at least a border\n */\n getColumnsWithBorders(sheetId) {\n const sheetBorders = this.borders[sheetId];\n if (!sheetBorders)\n return [];\n return Object.keys(sheetBorders).map((index) => parseInt(index, 10));\n }\n /**\n * Get the range of all the rows in the sheet\n */\n getRowsRange(sheetId) {\n const sheetBorders = this.borders[sheetId];\n if (!sheetBorders)\n return [];\n return range(0, this.getters.getNumberRows(sheetId) + 1);\n }\n /**\n * Move borders of a sheet horizontally.\n * @param sheetId\n * @param start starting column (included)\n * @param delta how much borders will be moved (negative if moved to the left)\n */\n shiftBordersHorizontally(sheetId, start, delta, { moveFirstLeftBorder } = {}) {\n const borders = this.borders[sheetId];\n if (!borders)\n return;\n if (delta < 0) {\n this.moveBordersOfColumn(sheetId, start, delta, \"vertical\", {\n destructive: false,\n });\n }\n this.getColumnsWithBorders(sheetId)\n .filter((col) => col >= start)\n .sort((a, b) => (delta < 0 ? a - b : b - a)) // start by the end when moving up\n .forEach((col) => {\n if ((col === start && moveFirstLeftBorder) || col !== start) {\n this.moveBordersOfColumn(sheetId, col, delta, \"vertical\");\n }\n this.moveBordersOfColumn(sheetId, col, delta, \"horizontal\");\n });\n }\n /**\n * Move borders of a sheet vertically.\n * @param sheetId\n * @param start starting row (included)\n * @param delta how much borders will be moved (negative if moved to the above)\n */\n shiftBordersVertically(sheetId, start, delta, { moveFirstTopBorder } = {}) {\n const borders = this.borders[sheetId];\n if (!borders)\n return;\n if (delta < 0) {\n this.moveBordersOfRow(sheetId, start, delta, \"horizontal\", {\n destructive: false,\n });\n }\n this.getRowsRange(sheetId)\n .filter((row) => row >= start)\n .sort((a, b) => (delta < 0 ? a - b : b - a)) // start by the end when moving up\n .forEach((row) => {\n if ((row === start && moveFirstTopBorder) || row !== start) {\n this.moveBordersOfRow(sheetId, row, delta, \"horizontal\");\n }\n this.moveBordersOfRow(sheetId, row, delta, \"vertical\");\n });\n }\n /**\n * Moves the borders (left if `vertical` or top if `horizontal` depending on\n * `borderDirection`) of all cells in an entire row `delta` rows to the right\n * (`delta` > 0) or to the left (`delta` < 0).\n * Note that as the left of a cell is the right of the cell-1, if the left is\n * moved the right is also moved. However, if `horizontal`, the bottom border\n * is not moved.\n * It does it by replacing the target border by the moved border. If the\n * argument `destructive` is given false, the target border is preserved if\n * the moved border is empty\n */\n moveBordersOfRow(sheetId, row, delta, borderDirection, { destructive } = { destructive: true }) {\n const borders = this.borders[sheetId];\n if (!borders)\n return;\n this.getColumnsWithBorders(sheetId).forEach((col) => {\n var _a, _b, _c, _d;\n const targetBorder = (_b = (_a = borders[col]) === null || _a === void 0 ? void 0 : _a[row + delta]) === null || _b === void 0 ? void 0 : _b[borderDirection];\n const movedBorder = (_d = (_c = borders[col]) === null || _c === void 0 ? void 0 : _c[row]) === null || _d === void 0 ? void 0 : _d[borderDirection];\n this.history.update(\"borders\", sheetId, col, row + delta, borderDirection, destructive ? movedBorder : movedBorder || targetBorder);\n this.history.update(\"borders\", sheetId, col, row, borderDirection, undefined);\n });\n }\n /**\n * Moves the borders (left if `vertical` or top if `horizontal` depending on\n * `borderDirection`) of all cells in an entire column `delta` columns below\n * (`delta` > 0) or above (`delta` < 0).\n * Note that as the top of a cell is the bottom of the cell-1, if the top is\n * moved the bottom is also moved. However, if `vertical`, the right border\n * is not moved.\n * It does it by replacing the target border by the moved border. If the\n * argument `destructive` is given false, the target border is preserved if\n * the moved border is empty\n */\n moveBordersOfColumn(sheetId, col, delta, borderDirection, { destructive } = { destructive: true }) {\n const borders = this.borders[sheetId];\n if (!borders)\n return;\n this.getRowsRange(sheetId).forEach((row) => {\n var _a, _b, _c, _d;\n const targetBorder = (_b = (_a = borders[col + delta]) === null || _a === void 0 ? void 0 : _a[row]) === null || _b === void 0 ? void 0 : _b[borderDirection];\n const movedBorder = (_d = (_c = borders[col]) === null || _c === void 0 ? void 0 : _c[row]) === null || _d === void 0 ? void 0 : _d[borderDirection];\n this.history.update(\"borders\", sheetId, col + delta, row, borderDirection, destructive ? movedBorder : movedBorder || targetBorder);\n this.history.update(\"borders\", sheetId, col, row, borderDirection, undefined);\n });\n }\n /**\n * Set the borders of a cell.\n * It overrides the current border if override == true.\n */\n setBorder(sheetId, col, row, border, override = true) {\n var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;\n if (override || !((_d = (_c = (_b = (_a = this.borders) === null || _a === void 0 ? void 0 : _a[sheetId]) === null || _b === void 0 ? void 0 : _b[col]) === null || _c === void 0 ? void 0 : _c[row]) === null || _d === void 0 ? void 0 : _d.vertical)) {\n this.history.update(\"borders\", sheetId, col, row, \"vertical\", border === null || border === void 0 ? void 0 : border.left);\n }\n if (override || !((_h = (_g = (_f = (_e = this.borders) === null || _e === void 0 ? void 0 : _e[sheetId]) === null || _f === void 0 ? void 0 : _f[col]) === null || _g === void 0 ? void 0 : _g[row]) === null || _h === void 0 ? void 0 : _h.horizontal)) {\n this.history.update(\"borders\", sheetId, col, row, \"horizontal\", border === null || border === void 0 ? void 0 : border.top);\n }\n if (override || !((_m = (_l = (_k = (_j = this.borders) === null || _j === void 0 ? void 0 : _j[sheetId]) === null || _k === void 0 ? void 0 : _k[col + 1]) === null || _l === void 0 ? void 0 : _l[row]) === null || _m === void 0 ? void 0 : _m.vertical)) {\n this.history.update(\"borders\", sheetId, col + 1, row, \"vertical\", border === null || border === void 0 ? void 0 : border.right);\n }\n if (override || !((_r = (_q = (_p = (_o = this.borders) === null || _o === void 0 ? void 0 : _o[sheetId]) === null || _p === void 0 ? void 0 : _p[col]) === null || _q === void 0 ? void 0 : _q[row + 1]) === null || _r === void 0 ? void 0 : _r.horizontal)) {\n this.history.update(\"borders\", sheetId, col, row + 1, \"horizontal\", border === null || border === void 0 ? void 0 : border.bottom);\n }\n }\n /**\n * Remove the borders of a zone\n */\n clearBorders(sheetId, zones) {\n for (let zone of zones) {\n for (let row = zone.top; row <= zone.bottom; row++) {\n this.history.update(\"borders\", sheetId, zone.right + 1, row, \"vertical\", undefined);\n for (let col = zone.left; col <= zone.right; col++) {\n this.history.update(\"borders\", sheetId, col, row, undefined);\n }\n }\n for (let col = zone.left; col <= zone.right; col++) {\n this.history.update(\"borders\", sheetId, col, zone.bottom + 1, \"horizontal\", undefined);\n }\n }\n }\n /**\n * Add a border to the existing one to a cell\n */\n addBorder(sheetId, col, row, border) {\n this.setBorder(sheetId, col, row, {\n ...this.getCellBorder({ sheetId, col, row }),\n ...border,\n });\n }\n /**\n * Set the borders of a zone by computing the borders to add from the given\n * command\n */\n setBorders(sheetId, zones, command) {\n if (command === \"clear\") {\n return this.clearBorders(sheetId, zones);\n }\n for (let zone of zones) {\n if (command === \"h\" || command === \"hv\" || command === \"all\") {\n for (let row = zone.top + 1; row <= zone.bottom; row++) {\n for (let col = zone.left; col <= zone.right; col++) {\n this.addBorder(sheetId, col, row, { top: DEFAULT_BORDER_DESC });\n }\n }\n }\n if (command === \"v\" || command === \"hv\" || command === \"all\") {\n for (let row = zone.top; row <= zone.bottom; row++) {\n for (let col = zone.left + 1; col <= zone.right; col++) {\n this.addBorder(sheetId, col, row, { left: DEFAULT_BORDER_DESC });\n }\n }\n }\n if (command === \"left\" || command === \"all\" || command === \"external\") {\n for (let row = zone.top; row <= zone.bottom; row++) {\n this.addBorder(sheetId, zone.left, row, { left: DEFAULT_BORDER_DESC });\n }\n }\n if (command === \"right\" || command === \"all\" || command === \"external\") {\n for (let row = zone.top; row <= zone.bottom; row++) {\n this.addBorder(sheetId, zone.right + 1, row, { left: DEFAULT_BORDER_DESC });\n }\n }\n if (command === \"top\" || command === \"all\" || command === \"external\") {\n for (let col = zone.left; col <= zone.right; col++) {\n this.addBorder(sheetId, col, zone.top, { top: DEFAULT_BORDER_DESC });\n }\n }\n if (command === \"bottom\" || command === \"all\" || command === \"external\") {\n for (let col = zone.left; col <= zone.right; col++) {\n this.addBorder(sheetId, col, zone.bottom + 1, { top: DEFAULT_BORDER_DESC });\n }\n }\n }\n }\n /**\n * Compute the borders to add to the given zone merged.\n */\n addBordersToMerge(sheetId, zone) {\n const { left, right, top, bottom } = zone;\n const bordersTopLeft = this.getCellBorder({ sheetId, col: left, row: top });\n const bordersBottomRight = this.getCellBorder({ sheetId, col: right, row: bottom });\n this.clearBorders(sheetId, [zone]);\n if (bordersTopLeft === null || bordersTopLeft === void 0 ? void 0 : bordersTopLeft.top) {\n this.setBorders(sheetId, [{ ...zone, bottom: top }], \"top\");\n }\n if (bordersTopLeft === null || bordersTopLeft === void 0 ? void 0 : bordersTopLeft.left) {\n this.setBorders(sheetId, [{ ...zone, right: left }], \"left\");\n }\n if ((bordersBottomRight === null || bordersBottomRight === void 0 ? void 0 : bordersBottomRight.bottom) || (bordersTopLeft === null || bordersTopLeft === void 0 ? void 0 : bordersTopLeft.bottom)) {\n this.setBorders(sheetId, [{ ...zone, top: bottom }], \"bottom\");\n }\n if ((bordersBottomRight === null || bordersBottomRight === void 0 ? void 0 : bordersBottomRight.right) || (bordersTopLeft === null || bordersTopLeft === void 0 ? void 0 : bordersTopLeft.right)) {\n this.setBorders(sheetId, [{ ...zone, left: right }], \"right\");\n }\n }\n // ---------------------------------------------------------------------------\n // Import/Export\n // ---------------------------------------------------------------------------\n import(data) {\n // Borders\n if (data.borders) {\n for (let sheet of data.sheets) {\n for (let [xc, cell] of Object.entries(sheet.cells)) {\n if (cell === null || cell === void 0 ? void 0 : cell.border) {\n const border = data.borders[cell.border];\n const { col, row } = toCartesian(xc);\n this.setBorder(sheet.id, col, row, border, false);\n }\n }\n }\n }\n // Merges\n for (let sheetData of data.sheets) {\n if (sheetData.merges) {\n for (let merge of sheetData.merges) {\n this.addBordersToMerge(sheetData.id, toZone(merge));\n }\n }\n }\n }\n export(data) {\n // Borders\n let borderId = 0;\n const borders = {};\n /**\n * Get the id of the given border. If the border does not exist, it creates\n * one.\n */\n function getBorderId(border) {\n for (let [key, value] of Object.entries(borders)) {\n if (stringify(value) === stringify(border)) {\n return parseInt(key, 10);\n }\n }\n borders[++borderId] = border;\n return borderId;\n }\n for (let sheet of data.sheets) {\n for (let col = 0; col < sheet.colNumber; col++) {\n for (let row = 0; row < sheet.rowNumber; row++) {\n const border = this.getCellBorder({ sheetId: sheet.id, col, row });\n if (border) {\n const xc = toXC(col, row);\n const cell = sheet.cells[xc];\n const borderId = getBorderId(border);\n if (cell) {\n cell.border = borderId;\n }\n else {\n sheet.cells[xc] = { border: borderId };\n }\n }\n }\n }\n }\n data.borders = borders;\n }\n exportForExcel(data) {\n this.export(data);\n }\n }\n BordersPlugin.getters = [\"getCellBorder\"];\n\n const nbspRegexp = new RegExp(String.fromCharCode(160), \"g\");\n /**\n * Core Plugin\n *\n * This is the most fundamental of all plugins. It defines how to interact with\n * cell and sheet content.\n */\n class CellPlugin extends CorePlugin {\n constructor() {\n super(...arguments);\n this.nextId = 1;\n this.cells = {};\n }\n adaptRanges(applyChange, sheetId) {\n for (const sheet of Object.keys(this.cells)) {\n for (const cell of Object.values(this.cells[sheet] || {})) {\n if (cell.isFormula) {\n for (const range of cell.dependencies) {\n if (!sheetId || range.sheetId === sheetId) {\n const change = applyChange(range);\n if (change.changeType !== \"NONE\") {\n this.history.update(\"cells\", sheet, cell.id, \"dependencies\", cell.dependencies.indexOf(range), change.range);\n }\n }\n }\n }\n }\n }\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"UPDATE_CELL\":\n case \"CLEAR_CELL\":\n return this.checkCellOutOfSheet(cmd.sheetId, cmd.col, cmd.row);\n default:\n return 0 /* CommandResult.Success */;\n }\n }\n handle(cmd) {\n switch (cmd.type) {\n case \"SET_FORMATTING\":\n if (\"style\" in cmd) {\n this.setStyle(cmd.sheetId, cmd.target, cmd.style);\n }\n if (\"format\" in cmd && cmd.format !== undefined) {\n this.setFormatter(cmd.sheetId, cmd.target, cmd.format);\n }\n break;\n case \"CLEAR_FORMATTING\":\n this.clearFormatting(cmd.sheetId, cmd.target);\n break;\n case \"ADD_COLUMNS_ROWS\":\n if (cmd.dimension === \"COL\") {\n this.handleAddColumnsRows(cmd, this.copyColumnStyle.bind(this));\n }\n else {\n this.handleAddColumnsRows(cmd, this.copyRowStyle.bind(this));\n }\n break;\n case \"UPDATE_CELL\":\n this.updateCell(cmd.sheetId, cmd.col, cmd.row, cmd);\n break;\n case \"CLEAR_CELL\":\n this.dispatch(\"UPDATE_CELL\", {\n sheetId: cmd.sheetId,\n col: cmd.col,\n row: cmd.row,\n content: \"\",\n style: null,\n format: \"\",\n });\n break;\n }\n }\n /**\n * Set a format to all the cells in a zone\n */\n setFormatter(sheetId, zones, format) {\n for (let zone of zones) {\n for (let row = zone.top; row <= zone.bottom; row++) {\n for (let col = zone.left; col <= zone.right; col++) {\n this.dispatch(\"UPDATE_CELL\", {\n sheetId,\n col,\n row,\n format,\n });\n }\n }\n }\n }\n /**\n * Clear the styles and format of zones\n */\n clearFormatting(sheetId, zones) {\n for (let zone of zones) {\n for (let col = zone.left; col <= zone.right; col++) {\n for (let row = zone.top; row <= zone.bottom; row++) {\n // commandHelpers.updateCell(sheetId, col, row, { style: undefined});\n this.dispatch(\"UPDATE_CELL\", {\n sheetId,\n col,\n row,\n style: null,\n format: \"\",\n });\n }\n }\n }\n }\n /**\n * Copy the style of the reference column/row to the new columns/rows.\n */\n handleAddColumnsRows(cmd, fn) {\n // The new elements have already been inserted in the sheet at this point.\n let insertedElements;\n let styleReference;\n if (cmd.position === \"before\") {\n insertedElements = range(cmd.base, cmd.base + cmd.quantity);\n styleReference = cmd.base + cmd.quantity;\n }\n else {\n insertedElements = range(cmd.base + 1, cmd.base + cmd.quantity + 1);\n styleReference = cmd.base;\n }\n fn(cmd.sheetId, styleReference, insertedElements);\n }\n // ---------------------------------------------------------------------------\n // Import/Export\n // ---------------------------------------------------------------------------\n import(data) {\n for (let sheet of data.sheets) {\n // cells\n for (let xc in sheet.cells) {\n const cellData = sheet.cells[xc];\n const { col, row } = toCartesian(xc);\n if ((cellData === null || cellData === void 0 ? void 0 : cellData.content) || (cellData === null || cellData === void 0 ? void 0 : cellData.format) || (cellData === null || cellData === void 0 ? void 0 : cellData.style)) {\n const cell = this.importCell(sheet.id, cellData, data.styles, data.formats);\n this.history.update(\"cells\", sheet.id, cell.id, cell);\n this.dispatch(\"UPDATE_CELL_POSITION\", {\n cellId: cell.id,\n col,\n row,\n sheetId: sheet.id,\n });\n }\n }\n }\n }\n export(data) {\n const styles = {};\n const formats = {};\n for (let _sheet of data.sheets) {\n const cells = {};\n const positions = Object.keys(this.cells[_sheet.id] || {})\n .map((cellId) => this.getters.getCellPosition(cellId))\n .sort((a, b) => (a.col === b.col ? a.row - b.row : a.col - b.col));\n for (const position of positions) {\n const cell = this.getters.getCell(position);\n const xc = toXC(position.col, position.row);\n cells[xc] = {\n style: cell.style ? getItemId(cell.style, styles) : undefined,\n format: cell.format ? getItemId(cell.format, formats) : undefined,\n content: cell.content || undefined,\n };\n }\n _sheet.cells = cells;\n }\n data.styles = styles;\n data.formats = formats;\n }\n importCell(sheetId, cellData, normalizedStyles, normalizedFormats) {\n const style = (cellData.style && normalizedStyles[cellData.style]) || undefined;\n const format = (cellData.format && normalizedFormats[cellData.format]) || undefined;\n const cellId = this.getNextUid();\n return this.createCell(cellId, (cellData === null || cellData === void 0 ? void 0 : cellData.content) || \"\", format, style, sheetId);\n }\n exportForExcel(data) {\n this.export(data);\n }\n // ---------------------------------------------------------------------------\n // GETTERS\n // ---------------------------------------------------------------------------\n getCells(sheetId) {\n return this.cells[sheetId] || {};\n }\n /**\n * get a cell by ID. Used in evaluation when evaluating an async cell, we need to be able to find it back after\n * starting an async evaluation even if it has been moved or re-allocated\n */\n getCellById(cellId) {\n // this must be as fast as possible\n for (const sheetId in this.cells) {\n const sheet = this.cells[sheetId];\n const cell = sheet[cellId];\n if (cell) {\n return cell;\n }\n }\n return undefined;\n }\n /*\n * Reconstructs the original formula string based on a normalized form and its dependencies\n */\n buildFormulaContent(sheetId, cell, dependencies) {\n const ranges = dependencies || [...cell.dependencies];\n return concat(cell.compiledFormula.tokens.map((token) => {\n if (token.type === \"REFERENCE\") {\n const range = ranges.shift();\n return this.getters.getRangeString(range, sheetId);\n }\n return token.value;\n }));\n }\n getFormulaCellContent(sheetId, cell) {\n return this.buildFormulaContent(sheetId, cell);\n }\n getCellStyle(position) {\n var _a;\n return ((_a = this.getters.getCell(position)) === null || _a === void 0 ? void 0 : _a.style) || {};\n }\n /**\n * Converts a zone to a XC coordinate system\n *\n * The conversion also treats merges as one single cell\n *\n * Examples:\n * {top:0,left:0,right:0,bottom:0} ==> A1\n * {top:0,left:0,right:1,bottom:1} ==> A1:B2\n *\n * if A1:B2 is a merge:\n * {top:0,left:0,right:1,bottom:1} ==> A1\n * {top:1,left:0,right:1,bottom:2} ==> A1:B3\n *\n * if A1:B2 and A4:B5 are merges:\n * {top:1,left:0,right:1,bottom:3} ==> A1:A5\n */\n zoneToXC(sheetId, zone, fixedParts = [{ colFixed: false, rowFixed: false }]) {\n zone = this.getters.expandZone(sheetId, zone);\n const topLeft = toXC(zone.left, zone.top, fixedParts[0]);\n const botRight = toXC(zone.right, zone.bottom, fixedParts.length > 1 ? fixedParts[1] : fixedParts[0]);\n const cellTopLeft = this.getters.getMainCellPosition({\n sheetId,\n col: zone.left,\n row: zone.top,\n });\n const cellBotRight = this.getters.getMainCellPosition({\n sheetId,\n col: zone.right,\n row: zone.bottom,\n });\n const sameCell = cellTopLeft.col === cellBotRight.col && cellTopLeft.row === cellBotRight.row;\n if (topLeft != botRight && !sameCell) {\n return topLeft + \":\" + botRight;\n }\n return topLeft;\n }\n setStyle(sheetId, target, style) {\n for (let zone of target) {\n for (let col = zone.left; col <= zone.right; col++) {\n for (let row = zone.top; row <= zone.bottom; row++) {\n const cell = this.getters.getCell({ sheetId, col, row });\n this.dispatch(\"UPDATE_CELL\", {\n sheetId,\n col,\n row,\n style: style ? { ...cell === null || cell === void 0 ? void 0 : cell.style, ...style } : undefined,\n });\n }\n }\n }\n }\n /**\n * Copy the style of one column to other columns.\n */\n copyColumnStyle(sheetId, refColumn, targetCols) {\n for (let row = 0; row < this.getters.getNumberRows(sheetId); row++) {\n const format = this.getFormat(sheetId, refColumn, row);\n if (format.style || format.format) {\n for (let col of targetCols) {\n this.dispatch(\"UPDATE_CELL\", { sheetId, col, row, ...format });\n }\n }\n }\n }\n /**\n * Copy the style of one row to other rows.\n */\n copyRowStyle(sheetId, refRow, targetRows) {\n for (let col = 0; col < this.getters.getNumberCols(sheetId); col++) {\n const format = this.getFormat(sheetId, col, refRow);\n if (format.style || format.format) {\n for (let row of targetRows) {\n this.dispatch(\"UPDATE_CELL\", { sheetId, col, row, ...format });\n }\n }\n }\n }\n /**\n * gets the currently used style/border of a cell based on it's coordinates\n */\n getFormat(sheetId, col, row) {\n const format = {};\n const position = this.getters.getMainCellPosition({ sheetId, col, row });\n const cell = this.getters.getCell(position);\n if (cell) {\n if (cell.style) {\n format[\"style\"] = cell.style;\n }\n if (cell.format) {\n format[\"format\"] = cell.format;\n }\n }\n return format;\n }\n getNextUid() {\n const id = this.nextId.toString();\n this.history.update(\"nextId\", this.nextId + 1);\n return id;\n }\n updateCell(sheetId, col, row, after) {\n var _a;\n const before = this.getters.getCell({ sheetId, col, row });\n const hasContent = \"content\" in after || \"formula\" in after;\n // Compute the new cell properties\n const afterContent = hasContent\n ? ((_a = after.content) === null || _a === void 0 ? void 0 : _a.replace(nbspRegexp, \"\")) || \"\"\n : (before === null || before === void 0 ? void 0 : before.content) || \"\";\n let style;\n if (after.style !== undefined) {\n style = after.style || undefined;\n }\n else {\n style = before ? before.style : undefined;\n }\n let format = (\"format\" in after ? after.format : before && before.format) || detectFormat(afterContent);\n /* Read the following IF as:\n * we need to remove the cell if it is completely empty, but we can know if it completely empty if:\n * - the command says the new content is empty and has no border/format/style\n * - the command has no content property, in this case\n * - either there wasn't a cell at this place and the command says border/format/style is empty\n * - or there was a cell at this place, but it's an empty cell and the command says border/format/style is empty\n * */\n if (((hasContent && !afterContent && !after.formula) ||\n (!hasContent && (!before || before.content === \"\"))) &&\n !style &&\n !format) {\n if (before) {\n this.history.update(\"cells\", sheetId, before.id, undefined);\n this.dispatch(\"UPDATE_CELL_POSITION\", {\n cellId: undefined,\n col,\n row,\n sheetId,\n });\n }\n return;\n }\n const cellId = (before === null || before === void 0 ? void 0 : before.id) || this.getNextUid();\n const cell = this.createCell(cellId, afterContent, format, style, sheetId);\n this.history.update(\"cells\", sheetId, cell.id, cell);\n this.dispatch(\"UPDATE_CELL_POSITION\", { cellId: cell.id, col, row, sheetId });\n }\n createCell(id, content, format, style, sheetId) {\n if (!content.startsWith(\"=\")) {\n return this.createLiteralCell(id, content, format, style);\n }\n try {\n return this.createFormulaCell(id, content, format, style, sheetId);\n }\n catch (error) {\n return this.createErrorFormula(id, content, format, style, error);\n }\n }\n createLiteralCell(id, content, format, style) {\n return {\n id,\n content,\n style,\n format,\n isFormula: false,\n };\n }\n createFormulaCell(id, content, format, style, sheetId) {\n const compiledFormula = compile(content);\n if (compiledFormula.dependencies.length) {\n return this.createFormulaCellWithDependencies(id, compiledFormula, format, style, sheetId);\n }\n return {\n id,\n content,\n style,\n format,\n isFormula: true,\n compiledFormula,\n dependencies: [],\n };\n }\n /**\n * Create a new formula cell with the content\n * being a computed property to rebuild the dependencies XC.\n */\n createFormulaCellWithDependencies(id, compiledFormula, format, style, sheetId) {\n const dependencies = compiledFormula.dependencies.map((xc) => this.getters.getRangeFromSheetXC(sheetId, xc));\n const buildFormulaContent = this.buildFormulaContent.bind(this);\n // Only for formulas with dependencies because\n // **the closure is expensive memory-wise**\n return {\n id,\n get content() {\n return buildFormulaContent(sheetId, {\n dependencies: this.dependencies,\n compiledFormula: this.compiledFormula,\n });\n },\n style,\n format,\n isFormula: true,\n compiledFormula,\n dependencies,\n };\n }\n createErrorFormula(id, content, format, style, error) {\n return {\n id,\n content,\n style,\n format,\n isFormula: true,\n compiledFormula: {\n dependencies: [],\n tokens: tokenize(content),\n execute: function () {\n throw error;\n },\n },\n dependencies: [],\n };\n }\n checkCellOutOfSheet(sheetId, col, row) {\n const sheet = this.getters.tryGetSheet(sheetId);\n if (!sheet)\n return 27 /* CommandResult.InvalidSheetId */;\n const sheetZone = this.getters.getSheetZone(sheetId);\n return isInside(col, row, sheetZone) ? 0 /* CommandResult.Success */ : 18 /* CommandResult.TargetOutOfSheet */;\n }\n }\n CellPlugin.getters = [\n \"zoneToXC\",\n \"getCells\",\n \"getFormulaCellContent\",\n \"getCellStyle\",\n \"buildFormulaContent\",\n \"getCellById\",\n ];\n\n class ChartPlugin extends CorePlugin {\n constructor() {\n super(...arguments);\n this.charts = {};\n this.createChart = chartFactory(this.getters);\n this.validateChartDefinition = (cmd) => validateChartDefinition(this, cmd.definition);\n }\n adaptRanges(applyChange) {\n for (const [chartId, chart] of Object.entries(this.charts)) {\n this.history.update(\"charts\", chartId, chart === null || chart === void 0 ? void 0 : chart.updateRanges(applyChange));\n }\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"CREATE_CHART\":\n return this.checkValidations(cmd, this.chainValidations(this.validateChartDefinition, this.checkChartDuplicate));\n case \"UPDATE_CHART\":\n return this.checkValidations(cmd, this.chainValidations(this.validateChartDefinition, this.checkChartExists));\n default:\n return 0 /* CommandResult.Success */;\n }\n }\n handle(cmd) {\n var _a;\n switch (cmd.type) {\n case \"CREATE_CHART\":\n this.addFigure(cmd.id, cmd.sheetId, cmd.position, cmd.size);\n this.addChart(cmd.id, cmd.definition);\n break;\n case \"UPDATE_CHART\": {\n this.addChart(cmd.id, cmd.definition);\n break;\n }\n case \"DUPLICATE_SHEET\": {\n const sheetFiguresFrom = this.getters.getFigures(cmd.sheetId);\n for (const fig of sheetFiguresFrom) {\n if (fig.tag === \"chart\") {\n const figureIdBase = fig.id.split(FIGURE_ID_SPLITTER).pop();\n const duplicatedFigureId = `${cmd.sheetIdTo}${FIGURE_ID_SPLITTER}${figureIdBase}`;\n const chart = (_a = this.charts[fig.id]) === null || _a === void 0 ? void 0 : _a.copyForSheetId(cmd.sheetIdTo);\n if (chart) {\n this.dispatch(\"CREATE_CHART\", {\n id: duplicatedFigureId,\n position: { x: fig.x, y: fig.y },\n size: { width: fig.width, height: fig.height },\n definition: chart.getDefinition(),\n sheetId: cmd.sheetIdTo,\n });\n }\n }\n }\n break;\n }\n case \"DELETE_FIGURE\":\n this.history.update(\"charts\", cmd.id, undefined);\n break;\n case \"DELETE_SHEET\":\n for (let id of this.getChartIds(cmd.sheetId)) {\n this.history.update(\"charts\", id, undefined);\n }\n break;\n }\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n getContextCreationChart(figureId) {\n var _a;\n return (_a = this.charts[figureId]) === null || _a === void 0 ? void 0 : _a.getContextCreation();\n }\n getChart(figureId) {\n return this.charts[figureId];\n }\n getChartType(figureId) {\n var _a;\n const type = (_a = this.charts[figureId]) === null || _a === void 0 ? void 0 : _a.type;\n if (!type) {\n throw new Error(\"Chart not defined.\");\n }\n return type;\n }\n isChartDefined(figureId) {\n return figureId in this.charts && this.charts !== undefined;\n }\n getChartIds(sheetId) {\n return Object.entries(this.charts)\n .filter(([, chart]) => (chart === null || chart === void 0 ? void 0 : chart.sheetId) === sheetId)\n .map(([id]) => id);\n }\n getChartDefinition(figureId) {\n var _a;\n const definition = (_a = this.charts[figureId]) === null || _a === void 0 ? void 0 : _a.getDefinition();\n if (!definition) {\n throw new Error(`There is no chart with the given figureId: ${figureId}`);\n }\n return definition;\n }\n // ---------------------------------------------------------------------------\n // Import/Export\n // ---------------------------------------------------------------------------\n import(data) {\n for (let sheet of data.sheets) {\n if (sheet.figures) {\n for (let figure of sheet.figures) {\n // TODO:\n // figure data should be external IMO => chart should be in sheet.chart\n // instead of in figure.data\n if (figure.tag === \"chart\") {\n this.charts[figure.id] = this.createChart(figure.id, figure.data, sheet.id);\n }\n }\n }\n }\n }\n export(data) {\n var _a;\n if (data.sheets) {\n for (let sheet of data.sheets) {\n // TODO This code is false, if two plugins want ot insert figures on the sheet, it will crash !\n const sheetFigures = this.getters.getFigures(sheet.id);\n const figures = [];\n for (let sheetFigure of sheetFigures) {\n const figure = sheetFigure;\n if (figure && figure.tag === \"chart\") {\n const data = (_a = this.charts[figure.id]) === null || _a === void 0 ? void 0 : _a.getDefinition();\n if (data) {\n figure.data = data;\n figures.push(figure);\n }\n }\n else {\n figures.push(figure);\n }\n }\n sheet.figures = figures;\n }\n }\n }\n exportForExcel(data) {\n var _a;\n for (let sheet of data.sheets) {\n const sheetFigures = this.getters.getFigures(sheet.id);\n const figures = [];\n for (let figure of sheetFigures) {\n if (figure && figure.tag === \"chart\") {\n const figureData = (_a = this.charts[figure.id]) === null || _a === void 0 ? void 0 : _a.getDefinitionForExcel();\n if (figureData) {\n figures.push({\n ...figure,\n data: figureData,\n });\n }\n }\n }\n sheet.charts = figures;\n }\n }\n // ---------------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------------\n /**\n * Add a figure with tag chart with the given id at the given position\n */\n addFigure(id, sheetId, position = { x: 0, y: 0 }, size = {\n width: DEFAULT_FIGURE_WIDTH,\n height: DEFAULT_FIGURE_HEIGHT,\n }) {\n if (this.getters.getFigure(sheetId, id)) {\n return;\n }\n const figure = {\n id,\n x: position.x,\n y: position.y,\n width: size.width,\n height: size.height,\n tag: \"chart\",\n };\n this.dispatch(\"CREATE_FIGURE\", { sheetId, figure });\n }\n /**\n * Add a chart in the local state. If a chart already exists, this chart is\n * replaced\n */\n addChart(id, definition) {\n const sheetId = this.getters.getFigureSheetId(id);\n if (sheetId) {\n this.history.update(\"charts\", id, this.createChart(id, definition, sheetId));\n }\n }\n checkChartDuplicate(cmd) {\n return this.getters.getFigureSheetId(cmd.id)\n ? 84 /* CommandResult.DuplicatedChartId */\n : 0 /* CommandResult.Success */;\n }\n checkChartExists(cmd) {\n return this.getters.getFigureSheetId(cmd.id)\n ? 0 /* CommandResult.Success */\n : 85 /* CommandResult.ChartDoesNotExist */;\n }\n }\n ChartPlugin.getters = [\n \"isChartDefined\",\n \"getChartDefinition\",\n \"getChartType\",\n \"getChartIds\",\n \"getChart\",\n \"getContextCreationChart\",\n ];\n\n // -----------------------------------------------------------------------------\n // Constants\n // -----------------------------------------------------------------------------\n function stringToNumber(value) {\n return value === \"\" ? NaN : Number(value);\n }\n class ConditionalFormatPlugin extends CorePlugin {\n constructor() {\n super(...arguments);\n this.cfRules = {};\n }\n loopThroughRangesOfSheet(sheetId, applyChange) {\n for (const rule of this.cfRules[sheetId]) {\n for (const range of rule.ranges) {\n const change = applyChange(range);\n switch (change.changeType) {\n case \"REMOVE\":\n let copy = rule.ranges.slice();\n copy.splice(rule.ranges.indexOf(range), 1);\n if (copy.length >= 1) {\n this.history.update(\"cfRules\", sheetId, this.cfRules[sheetId].indexOf(rule), \"ranges\", copy);\n }\n else {\n this.removeConditionalFormatting(rule.id, sheetId);\n }\n break;\n case \"RESIZE\":\n case \"MOVE\":\n case \"CHANGE\":\n this.history.update(\"cfRules\", sheetId, this.cfRules[sheetId].indexOf(rule), \"ranges\", rule.ranges.indexOf(range), change.range);\n break;\n }\n }\n }\n }\n adaptRanges(applyChange, sheetId) {\n if (sheetId) {\n this.loopThroughRangesOfSheet(sheetId, applyChange);\n }\n else {\n for (const sheetId of Object.keys(this.cfRules)) {\n this.loopThroughRangesOfSheet(sheetId, applyChange);\n }\n }\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"ADD_CONDITIONAL_FORMAT\":\n return this.checkValidations(cmd, this.checkCFRule, this.checkEmptyRange);\n case \"MOVE_CONDITIONAL_FORMAT\":\n return this.checkValidReordering(cmd.cfId, cmd.direction, cmd.sheetId);\n }\n return 0 /* CommandResult.Success */;\n }\n handle(cmd) {\n switch (cmd.type) {\n case \"CREATE_SHEET\":\n this.cfRules[cmd.sheetId] = [];\n break;\n case \"DUPLICATE_SHEET\":\n this.history.update(\"cfRules\", cmd.sheetIdTo, []);\n for (const cf of this.getConditionalFormats(cmd.sheetId)) {\n this.addConditionalFormatting(cf, cmd.sheetIdTo);\n }\n break;\n case \"DELETE_SHEET\":\n const cfRules = Object.assign({}, this.cfRules);\n delete cfRules[cmd.sheetId];\n this.history.update(\"cfRules\", cfRules);\n break;\n case \"ADD_CONDITIONAL_FORMAT\":\n const cf = {\n ...cmd.cf,\n ranges: cmd.ranges.map((rangeData) => this.getters.getRangeString(this.getters.getRangeFromRangeData(rangeData), cmd.sheetId)),\n };\n this.addConditionalFormatting(cf, cmd.sheetId);\n break;\n case \"REMOVE_CONDITIONAL_FORMAT\":\n this.removeConditionalFormatting(cmd.id, cmd.sheetId);\n break;\n case \"MOVE_CONDITIONAL_FORMAT\":\n this.reorderConditionalFormatting(cmd.cfId, cmd.direction, cmd.sheetId);\n break;\n }\n }\n import(data) {\n for (let sheet of data.sheets) {\n this.cfRules[sheet.id] = sheet.conditionalFormats.map((rule) => this.mapToConditionalFormatInternal(sheet.id, rule));\n }\n }\n export(data) {\n if (data.sheets) {\n for (let sheet of data.sheets) {\n if (this.cfRules[sheet.id]) {\n sheet.conditionalFormats = this.cfRules[sheet.id].map((rule) => this.mapToConditionalFormat(sheet.id, rule));\n }\n }\n }\n }\n exportForExcel(data) {\n this.export(data);\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n /**\n * Returns all the conditional format rules defined for the current sheet to display the user\n */\n getConditionalFormats(sheetId) {\n var _a;\n return ((_a = this.cfRules[sheetId]) === null || _a === void 0 ? void 0 : _a.map((cf) => this.mapToConditionalFormat(sheetId, cf))) || [];\n }\n getRulesSelection(sheetId, selection) {\n const ruleIds = new Set();\n selection.forEach((zone) => {\n const zoneRuleId = this.getRulesByZone(sheetId, zone);\n zoneRuleId.forEach((ruleId) => {\n ruleIds.add(ruleId);\n });\n });\n return Array.from(ruleIds);\n }\n getRulesByZone(sheetId, zone) {\n const ruleIds = new Set();\n for (let row = zone.top; row <= zone.bottom; row++) {\n for (let col = zone.left; col <= zone.right; col++) {\n const cellRules = this.getRulesByCell(sheetId, col, row);\n cellRules.forEach((rule) => {\n ruleIds.add(rule.id);\n });\n }\n }\n return ruleIds;\n }\n getRulesByCell(sheetId, cellCol, cellRow) {\n const rules = [];\n for (let cf of this.cfRules[sheetId]) {\n for (let range of cf.ranges) {\n if (isInside(cellCol, cellRow, range.zone)) {\n rules.push(cf);\n }\n }\n }\n return new Set(rules.map((rule) => {\n return this.mapToConditionalFormat(sheetId, rule);\n }));\n }\n // ---------------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------------\n mapToConditionalFormat(sheetId, cf) {\n return {\n ...cf,\n ranges: cf.ranges.map((range) => {\n return this.getters.getRangeString(range, sheetId);\n }),\n };\n }\n mapToConditionalFormatInternal(sheet, cf) {\n const conditionalFormat = {\n ...cf,\n ranges: cf.ranges.map((range) => {\n return this.getters.getRangeFromSheetXC(sheet, range);\n }),\n };\n return conditionalFormat;\n }\n /**\n * Add or replace a conditional format rule\n */\n addConditionalFormatting(cf, sheet) {\n const currentCF = this.cfRules[sheet].slice();\n const replaceIndex = currentCF.findIndex((c) => c.id === cf.id);\n const newCF = this.mapToConditionalFormatInternal(sheet, cf);\n if (replaceIndex > -1) {\n currentCF.splice(replaceIndex, 1, newCF);\n }\n else {\n currentCF.push(newCF);\n }\n this.history.update(\"cfRules\", sheet, currentCF);\n }\n checkValidReordering(cfId, direction, sheetId) {\n if (!this.cfRules[sheetId])\n return 27 /* CommandResult.InvalidSheetId */;\n const ruleIndex = this.cfRules[sheetId].findIndex((cf) => cf.id === cfId);\n if (ruleIndex === -1)\n return 71 /* CommandResult.InvalidConditionalFormatId */;\n const cfIndex2 = direction === \"up\" ? ruleIndex - 1 : ruleIndex + 1;\n if (cfIndex2 < 0 || cfIndex2 >= this.cfRules[sheetId].length) {\n return 71 /* CommandResult.InvalidConditionalFormatId */;\n }\n return 0 /* CommandResult.Success */;\n }\n checkEmptyRange(cmd) {\n return cmd.ranges.length ? 0 /* CommandResult.Success */ : 24 /* CommandResult.EmptyRange */;\n }\n checkCFRule(cmd) {\n const rule = cmd.cf.rule;\n switch (rule.type) {\n case \"CellIsRule\":\n return this.checkValidations(rule, this.checkOperatorArgsNumber(2, [\"Between\", \"NotBetween\"]), this.checkOperatorArgsNumber(1, [\n \"BeginsWith\",\n \"ContainsText\",\n \"EndsWith\",\n \"GreaterThan\",\n \"GreaterThanOrEqual\",\n \"LessThan\",\n \"LessThanOrEqual\",\n \"NotContains\",\n ]), this.checkOperatorArgsNumber(0, [\"IsEmpty\", \"IsNotEmpty\"]));\n case \"ColorScaleRule\": {\n return this.checkValidations(rule, this.chainValidations(this.checkThresholds(this.checkFormulaCompilation)), this.chainValidations(this.checkThresholds(this.checkNaN), this.batchValidations(this.checkMinBiggerThanMax, this.checkMinBiggerThanMid, this.checkMidBiggerThanMax\n // Those three validations can be factorized further\n )));\n }\n case \"IconSetRule\": {\n return this.checkValidations(rule, this.chainValidations(this.checkInflectionPoints(this.checkNaN), this.checkLowerBiggerThanUpper), this.chainValidations(this.checkInflectionPoints(this.checkFormulaCompilation)));\n }\n }\n return 0 /* CommandResult.Success */;\n }\n checkOperatorArgsNumber(expectedNumber, operators) {\n if (expectedNumber > 2) {\n throw new Error(\"Checking more than 2 arguments is currently not supported. Add the appropriate CommandResult if you want to.\");\n }\n return (rule) => {\n if (operators.includes(rule.operator)) {\n const errors = [];\n const isEmpty = (value) => value === undefined || value === \"\";\n if (expectedNumber >= 1 && isEmpty(rule.values[0])) {\n errors.push(51 /* CommandResult.FirstArgMissing */);\n }\n if (expectedNumber >= 2 && isEmpty(rule.values[1])) {\n errors.push(52 /* CommandResult.SecondArgMissing */);\n }\n return errors.length ? errors : 0 /* CommandResult.Success */;\n }\n return 0 /* CommandResult.Success */;\n };\n }\n checkNaN(threshold, thresholdName) {\n if ([\"number\", \"percentage\", \"percentile\"].includes(threshold.type) &&\n (threshold.value === \"\" || isNaN(threshold.value))) {\n switch (thresholdName) {\n case \"min\":\n return 53 /* CommandResult.MinNaN */;\n case \"max\":\n return 55 /* CommandResult.MaxNaN */;\n case \"mid\":\n return 54 /* CommandResult.MidNaN */;\n case \"upperInflectionPoint\":\n return 56 /* CommandResult.ValueUpperInflectionNaN */;\n case \"lowerInflectionPoint\":\n return 57 /* CommandResult.ValueLowerInflectionNaN */;\n }\n }\n return 0 /* CommandResult.Success */;\n }\n checkFormulaCompilation(threshold, thresholdName) {\n if (threshold.type !== \"formula\")\n return 0 /* CommandResult.Success */;\n try {\n compile(threshold.value || \"\");\n }\n catch (error) {\n switch (thresholdName) {\n case \"min\":\n return 58 /* CommandResult.MinInvalidFormula */;\n case \"max\":\n return 60 /* CommandResult.MaxInvalidFormula */;\n case \"mid\":\n return 59 /* CommandResult.MidInvalidFormula */;\n case \"upperInflectionPoint\":\n return 61 /* CommandResult.ValueUpperInvalidFormula */;\n case \"lowerInflectionPoint\":\n return 62 /* CommandResult.ValueLowerInvalidFormula */;\n }\n }\n return 0 /* CommandResult.Success */;\n }\n checkThresholds(check) {\n return this.batchValidations((rule) => check(rule.minimum, \"min\"), (rule) => check(rule.maximum, \"max\"), (rule) => (rule.midpoint ? check(rule.midpoint, \"mid\") : 0 /* CommandResult.Success */));\n }\n checkInflectionPoints(check) {\n return this.batchValidations((rule) => check(rule.lowerInflectionPoint, \"lowerInflectionPoint\"), (rule) => check(rule.upperInflectionPoint, \"upperInflectionPoint\"));\n }\n checkLowerBiggerThanUpper(rule) {\n const minValue = rule.lowerInflectionPoint.value;\n const maxValue = rule.upperInflectionPoint.value;\n if ([\"number\", \"percentage\", \"percentile\"].includes(rule.lowerInflectionPoint.type) &&\n rule.lowerInflectionPoint.type === rule.upperInflectionPoint.type &&\n Number(minValue) > Number(maxValue)) {\n return 48 /* CommandResult.LowerBiggerThanUpper */;\n }\n return 0 /* CommandResult.Success */;\n }\n checkMinBiggerThanMax(rule) {\n const minValue = rule.minimum.value;\n const maxValue = rule.maximum.value;\n if ([\"number\", \"percentage\", \"percentile\"].includes(rule.minimum.type) &&\n rule.minimum.type === rule.maximum.type &&\n stringToNumber(minValue) >= stringToNumber(maxValue)) {\n return 47 /* CommandResult.MinBiggerThanMax */;\n }\n return 0 /* CommandResult.Success */;\n }\n checkMidBiggerThanMax(rule) {\n var _a;\n const midValue = (_a = rule.midpoint) === null || _a === void 0 ? void 0 : _a.value;\n const maxValue = rule.maximum.value;\n if (rule.midpoint &&\n [\"number\", \"percentage\", \"percentile\"].includes(rule.midpoint.type) &&\n rule.midpoint.type === rule.maximum.type &&\n stringToNumber(midValue) >= stringToNumber(maxValue)) {\n return 49 /* CommandResult.MidBiggerThanMax */;\n }\n return 0 /* CommandResult.Success */;\n }\n checkMinBiggerThanMid(rule) {\n var _a;\n const minValue = rule.minimum.value;\n const midValue = (_a = rule.midpoint) === null || _a === void 0 ? void 0 : _a.value;\n if (rule.midpoint &&\n [\"number\", \"percentage\", \"percentile\"].includes(rule.midpoint.type) &&\n rule.minimum.type === rule.midpoint.type &&\n stringToNumber(minValue) >= stringToNumber(midValue)) {\n return 50 /* CommandResult.MinBiggerThanMid */;\n }\n return 0 /* CommandResult.Success */;\n }\n removeConditionalFormatting(id, sheet) {\n const cfIndex = this.cfRules[sheet].findIndex((s) => s.id === id);\n if (cfIndex !== -1) {\n const currentCF = this.cfRules[sheet].slice();\n currentCF.splice(cfIndex, 1);\n this.history.update(\"cfRules\", sheet, currentCF);\n }\n }\n reorderConditionalFormatting(cfId, direction, sheetId) {\n const cfIndex1 = this.cfRules[sheetId].findIndex((s) => s.id === cfId);\n const cfIndex2 = direction === \"up\" ? cfIndex1 - 1 : cfIndex1 + 1;\n if (cfIndex2 < 0 || cfIndex2 >= this.cfRules[sheetId].length)\n return;\n if (cfIndex1 !== -1 && cfIndex2 !== -1) {\n const currentCF = [...this.cfRules[sheetId]];\n const tmp = currentCF[cfIndex1];\n currentCF[cfIndex1] = currentCF[cfIndex2];\n currentCF[cfIndex2] = tmp;\n this.history.update(\"cfRules\", sheetId, currentCF);\n }\n }\n }\n ConditionalFormatPlugin.getters = [\"getConditionalFormats\", \"getRulesSelection\", \"getRulesByCell\"];\n\n class FigurePlugin extends CorePlugin {\n constructor() {\n super(...arguments);\n this.figures = {};\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"CREATE_FIGURE\":\n return this.checkFigureDuplicate(cmd.figure.id);\n case \"UPDATE_FIGURE\":\n case \"DELETE_FIGURE\":\n return this.checkFigureExists(cmd.sheetId, cmd.id);\n default:\n return 0 /* CommandResult.Success */;\n }\n }\n handle(cmd) {\n switch (cmd.type) {\n case \"CREATE_SHEET\":\n this.figures[cmd.sheetId] = {};\n break;\n case \"DELETE_SHEET\":\n this.deleteSheet(cmd.sheetId);\n break;\n case \"CREATE_FIGURE\":\n this.addFigure(cmd.figure, cmd.sheetId);\n break;\n case \"UPDATE_FIGURE\":\n const { type, sheetId, ...update } = cmd;\n const figure = update;\n this.updateFigure(sheetId, figure);\n break;\n case \"DELETE_FIGURE\":\n this.removeFigure(cmd.id, cmd.sheetId);\n break;\n }\n }\n updateFigure(sheetId, figure) {\n if (!(\"id\" in figure)) {\n return;\n }\n for (const [key, value] of Object.entries(figure)) {\n switch (key) {\n case \"x\":\n case \"y\":\n if (value !== undefined) {\n this.history.update(\"figures\", sheetId, figure.id, key, Math.max(value, 0));\n }\n break;\n case \"width\":\n case \"height\":\n if (value !== undefined) {\n this.history.update(\"figures\", sheetId, figure.id, key, value);\n }\n break;\n }\n }\n }\n addFigure(figure, sheetId) {\n this.history.update(\"figures\", sheetId, figure.id, figure);\n }\n deleteSheet(sheetId) {\n this.history.update(\"figures\", sheetId, undefined);\n }\n removeFigure(id, sheetId) {\n this.history.update(\"figures\", sheetId, id, undefined);\n }\n checkFigureExists(sheetId, figureId) {\n var _a;\n if (((_a = this.figures[sheetId]) === null || _a === void 0 ? void 0 : _a[figureId]) === undefined) {\n return 70 /* CommandResult.FigureDoesNotExist */;\n }\n return 0 /* CommandResult.Success */;\n }\n checkFigureDuplicate(figureId) {\n if (Object.values(this.figures).find((sheet) => sheet === null || sheet === void 0 ? void 0 : sheet[figureId])) {\n return 82 /* CommandResult.DuplicatedFigureId */;\n }\n return 0 /* CommandResult.Success */;\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n getFigures(sheetId) {\n return Object.values(this.figures[sheetId] || {}).filter(isDefined$1);\n }\n getFigure(sheetId, figureId) {\n var _a;\n return (_a = this.figures[sheetId]) === null || _a === void 0 ? void 0 : _a[figureId];\n }\n getFigureSheetId(figureId) {\n return Object.keys(this.figures).find((sheetId) => { var _a; return ((_a = this.figures[sheetId]) === null || _a === void 0 ? void 0 : _a[figureId]) !== undefined; });\n }\n // ---------------------------------------------------------------------------\n // Import/Export\n // ---------------------------------------------------------------------------\n import(data) {\n for (let sheet of data.sheets) {\n const figures = {};\n sheet.figures.forEach((figure) => {\n figures[figure.id] = figure;\n });\n this.figures[sheet.id] = figures;\n }\n }\n export(data) {\n for (const sheet of data.sheets) {\n for (const figure of this.getFigures(sheet.id)) {\n const data = undefined;\n sheet.figures.push({ ...figure, data });\n }\n }\n }\n exportForExcel(data) {\n this.export(data);\n }\n }\n FigurePlugin.getters = [\"getFigures\", \"getFigure\", \"getFigureSheetId\"];\n\n class FilterTable {\n constructor(zone) {\n this.filters = [];\n this.zone = zone;\n const uuid = new UuidGenerator();\n this.id = uuid.uuidv4();\n for (const i of range(zone.left, zone.right + 1)) {\n const filterZone = { ...this.zone, left: i, right: i };\n this.filters.push(new Filter(uuid.uuidv4(), filterZone));\n }\n }\n /** Get zone of the table without the headers */\n get contentZone() {\n if (this.zone.bottom === this.zone.top) {\n return undefined;\n }\n return { ...this.zone, top: this.zone.top + 1 };\n }\n getFilterId(col) {\n var _a;\n return (_a = this.filters.find((filter) => filter.col === col)) === null || _a === void 0 ? void 0 : _a.id;\n }\n clone() {\n return new FilterTable(this.zone);\n }\n }\n class Filter {\n constructor(id, zone) {\n if (zone.left !== zone.right) {\n throw new Error(\"Can only define a filter on a single column\");\n }\n this.id = id;\n this.zoneWithHeaders = zone;\n }\n get col() {\n return this.zoneWithHeaders.left;\n }\n /** Filtered zone, ie. zone of the filter without the header */\n get filteredZone() {\n const zone = this.zoneWithHeaders;\n if (zone.bottom === zone.top) {\n return undefined;\n }\n return { ...zone, top: zone.top + 1 };\n }\n }\n\n class FiltersPlugin extends CorePlugin {\n constructor() {\n super(...arguments);\n this.tables = {};\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"CREATE_FILTER_TABLE\":\n if (!areZonesContinuous(...cmd.target)) {\n return 81 /* CommandResult.NonContinuousTargets */;\n }\n const zone = union(...cmd.target);\n const checkFilterOverlap = () => {\n if (this.getFilterTables(cmd.sheetId).some((filter) => overlap(filter.zone, zone))) {\n return 78 /* CommandResult.FilterOverlap */;\n }\n return 0 /* CommandResult.Success */;\n };\n const checkMergeInFilter = () => {\n const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, zone);\n for (let merge of mergesInTarget) {\n if (overlap(zone, merge)) {\n return 80 /* CommandResult.MergeInFilter */;\n }\n }\n return 0 /* CommandResult.Success */;\n };\n return this.checkValidations(cmd, checkFilterOverlap, checkMergeInFilter);\n case \"ADD_MERGE\":\n for (let merge of cmd.target) {\n for (let filterTable of this.getFilterTables(cmd.sheetId)) {\n if (overlap(filterTable.zone, merge)) {\n return 80 /* CommandResult.MergeInFilter */;\n }\n }\n }\n break;\n }\n return 0 /* CommandResult.Success */;\n }\n handle(cmd) {\n switch (cmd.type) {\n case \"CREATE_SHEET\":\n this.history.update(\"tables\", cmd.sheetId, {});\n break;\n case \"DELETE_SHEET\":\n const filterTables = { ...this.tables };\n delete filterTables[cmd.sheetId];\n this.history.update(\"tables\", filterTables);\n break;\n case \"DUPLICATE_SHEET\":\n this.history.update(\"tables\", cmd.sheetIdTo, deepCopy(this.tables[cmd.sheetId]));\n break;\n case \"ADD_COLUMNS_ROWS\":\n this.onAddColumnsRows(cmd);\n break;\n case \"REMOVE_COLUMNS_ROWS\":\n this.onDeleteColumnsRows(cmd);\n break;\n case \"CREATE_FILTER_TABLE\": {\n const zone = union(...cmd.target);\n const newFilterTable = this.createFilterTable(zone);\n this.history.update(\"tables\", cmd.sheetId, newFilterTable.id, newFilterTable);\n break;\n }\n case \"REMOVE_FILTER_TABLE\": {\n const tables = {};\n for (const filterTable of this.getFilterTables(cmd.sheetId)) {\n if (cmd.target.every((zone) => !intersection(zone, filterTable.zone))) {\n tables[filterTable.id] = filterTable;\n }\n }\n this.history.update(\"tables\", cmd.sheetId, tables);\n break;\n }\n case \"UPDATE_CELL\": {\n const sheetId = cmd.sheetId;\n for (let table of this.getFilterTables(sheetId)) {\n if (this.canUpdateCellCmdExtendTable(cmd, table)) {\n this.extendTableDown(sheetId, table);\n }\n }\n break;\n }\n }\n }\n getFilters(sheetId) {\n return this.getFilterTables(sheetId)\n .map((filterTable) => filterTable.filters)\n .flat();\n }\n getFilterTables(sheetId) {\n return this.tables[sheetId] ? Object.values(this.tables[sheetId]).filter(isDefined$1) : [];\n }\n getFilter(position) {\n var _a;\n return (_a = this.getFilterTable(position)) === null || _a === void 0 ? void 0 : _a.filters.find((filter) => filter.col === position.col);\n }\n getFilterId(position) {\n var _a;\n return (_a = this.getFilter(position)) === null || _a === void 0 ? void 0 : _a.id;\n }\n getFilterTable({ sheetId, col, row }) {\n return this.getFilterTables(sheetId).find((filterTable) => isInside(col, row, filterTable.zone));\n }\n /** Get the filter tables that are fully inside the given zone */\n getFilterTablesInZone(sheetId, zone) {\n return this.getFilterTables(sheetId).filter((filterTable) => isZoneInside(filterTable.zone, zone));\n }\n doesZonesContainFilter(sheetId, zones) {\n for (const zone of zones) {\n for (const filterTable of this.getFilterTables(sheetId)) {\n if (intersection(zone, filterTable.zone)) {\n return true;\n }\n }\n }\n return false;\n }\n onAddColumnsRows(cmd) {\n for (const filterTable of this.getFilterTables(cmd.sheetId)) {\n const zone = expandZoneOnInsertion(filterTable.zone, cmd.dimension === \"COL\" ? \"left\" : \"top\", cmd.base, cmd.position, cmd.quantity);\n const filters = [];\n for (const filter of filterTable.filters) {\n const filterZone = expandZoneOnInsertion(filter.zoneWithHeaders, cmd.dimension === \"COL\" ? \"left\" : \"top\", cmd.base, cmd.position, cmd.quantity);\n filters.push(new Filter(filter.id, filterZone));\n }\n // Add filters for new columns\n if (filters.length < zoneToDimension(zone).width) {\n for (let col = zone.left; col <= zone.right; col++) {\n if (!filters.find((filter) => filter.col === col)) {\n filters.push(new Filter(this.uuidGenerator.uuidv4(), { ...zone, left: col, right: col }));\n }\n }\n filters.sort((f1, f2) => f1.col - f2.col);\n }\n this.history.update(\"tables\", cmd.sheetId, filterTable.id, \"zone\", zone);\n this.history.update(\"tables\", cmd.sheetId, filterTable.id, \"filters\", filters);\n }\n }\n onDeleteColumnsRows(cmd) {\n for (const table of this.getFilterTables(cmd.sheetId)) {\n const zone = reduceZoneOnDeletion(table.zone, cmd.dimension === \"COL\" ? \"left\" : \"top\", cmd.elements);\n if (!zone) {\n const tables = { ...this.tables[cmd.sheetId] };\n delete tables[table.id];\n this.history.update(\"tables\", cmd.sheetId, tables);\n }\n else {\n if (zoneToXc(zone) !== zoneToXc(table.zone)) {\n const filters = [];\n for (const filter of table.filters) {\n const newFilterZone = reduceZoneOnDeletion(filter.zoneWithHeaders, cmd.dimension === \"COL\" ? \"left\" : \"top\", cmd.elements);\n if (newFilterZone) {\n filters.push(new Filter(filter.id, newFilterZone));\n }\n }\n this.history.update(\"tables\", cmd.sheetId, table.id, \"zone\", zone);\n this.history.update(\"tables\", cmd.sheetId, table.id, \"filters\", filters);\n }\n }\n }\n }\n createFilterTable(zone) {\n return new FilterTable(zone);\n }\n /** Extend a table down one row */\n extendTableDown(sheetId, table) {\n const newZone = { ...table.zone, bottom: table.zone.bottom + 1 };\n this.history.update(\"tables\", sheetId, table.id, \"zone\", newZone);\n for (let filterIndex = 0; filterIndex < table.filters.length; filterIndex++) {\n const filter = table.filters[filterIndex];\n const newFilterZone = {\n ...filter.zoneWithHeaders,\n bottom: filter.zoneWithHeaders.bottom + 1,\n };\n this.history.update(\"tables\", sheetId, table.id, \"filters\", filterIndex, \"zoneWithHeaders\", newFilterZone);\n }\n return;\n }\n /**\n * Check if an UpdateCell command should cause the given table to be extended by one row.\n *\n * The table should be extended if all of these conditions are true:\n * 1) The updated cell is right below the table\n * 2) The command adds a content to the cell\n * 3) No cell right below the table had any content before the command\n * 4) Extending the table down would not overlap with another filter\n * 5) Extending the table down would not overlap with a merge\n *\n */\n canUpdateCellCmdExtendTable({ content: newCellContent, sheetId, col, row }, table) {\n var _a;\n if (!newCellContent) {\n return;\n }\n const zone = table.zone;\n if (!(zone.bottom + 1 === row && col >= zone.left && col <= zone.right)) {\n return false;\n }\n for (const col of range(zone.left, zone.right + 1)) {\n const position = { sheetId, col, row };\n // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content\n const cellContent = (_a = this.getters.getCell(position)) === null || _a === void 0 ? void 0 : _a.content;\n if (cellContent) {\n return false;\n }\n if (this.getters.getFilter(position)) {\n return false;\n }\n if (this.getters.isInMerge(position)) {\n return false;\n }\n }\n return true;\n }\n // ---------------------------------------------------------------------------\n // Import/Export\n // ---------------------------------------------------------------------------\n import(data) {\n for (const sheet of data.sheets) {\n for (const filterTableData of sheet.filterTables || []) {\n const table = this.createFilterTable(toZone(filterTableData.range));\n this.history.update(\"tables\", sheet.id, table.id, table);\n }\n }\n }\n export(data) {\n for (const sheet of data.sheets) {\n for (const filterTable of this.getFilterTables(sheet.id)) {\n sheet.filterTables.push({\n range: zoneToXc(filterTable.zone),\n });\n }\n }\n }\n exportForExcel(data) {\n this.export(data);\n }\n }\n FiltersPlugin.getters = [\n \"doesZonesContainFilter\",\n \"getFilter\",\n \"getFilters\",\n \"getFilterTable\",\n \"getFilterTables\",\n \"getFilterTablesInZone\",\n \"getFilterId\",\n ];\n\n class HeaderSizePlugin extends CorePlugin {\n constructor() {\n super(...arguments);\n this.sizes = {};\n }\n handle(cmd) {\n var _a, _b, _c;\n switch (cmd.type) {\n case \"CREATE_SHEET\": {\n const computedSizes = this.computeSheetSizes(cmd.sheetId);\n const sizes = {\n COL: computedSizes.COL.map((size) => ({\n manualSize: undefined,\n computedSize: lazy(size),\n })),\n ROW: computedSizes.ROW.map((size) => ({\n manualSize: undefined,\n computedSize: lazy(size),\n })),\n };\n this.history.update(\"sizes\", cmd.sheetId, sizes);\n break;\n }\n case \"DUPLICATE_SHEET\":\n // make sure the values are computed in case the original sheet is deleted\n for (const row of this.sizes[cmd.sheetId].ROW) {\n row.computedSize();\n }\n for (const col of this.sizes[cmd.sheetId].COL) {\n col.computedSize();\n }\n this.history.update(\"sizes\", cmd.sheetIdTo, deepCopy(this.sizes[cmd.sheetId]));\n break;\n case \"DELETE_SHEET\":\n const sizes = { ...this.sizes };\n delete sizes[cmd.sheetId];\n this.history.update(\"sizes\", sizes);\n break;\n case \"REMOVE_COLUMNS_ROWS\": {\n let sizes = [...this.sizes[cmd.sheetId][cmd.dimension]];\n for (let headerIndex of [...cmd.elements].sort((a, b) => b - a)) {\n sizes.splice(headerIndex, 1);\n }\n const min = Math.min(...cmd.elements);\n sizes = sizes.map((size, row) => {\n if (cmd.dimension === \"ROW\" && row >= min) {\n // invalidate sizes\n return {\n manualSize: size.manualSize,\n computedSize: lazy(() => this.getRowTallestCellSize(cmd.sheetId, row)),\n };\n }\n return size;\n });\n this.history.update(\"sizes\", cmd.sheetId, cmd.dimension, sizes);\n break;\n }\n case \"ADD_COLUMNS_ROWS\": {\n let sizes = [...this.sizes[cmd.sheetId][cmd.dimension]];\n const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);\n const baseSize = sizes[cmd.base];\n sizes.splice(addIndex, 0, ...Array(cmd.quantity).fill(baseSize));\n sizes = sizes.map((size, row) => {\n if (cmd.dimension === \"ROW\" && row > cmd.base + cmd.quantity) {\n // invalidate sizes\n return {\n manualSize: size.manualSize,\n computedSize: lazy(() => this.getRowTallestCellSize(cmd.sheetId, row)),\n };\n }\n return size;\n });\n this.history.update(\"sizes\", cmd.sheetId, cmd.dimension, sizes);\n break;\n }\n case \"RESIZE_COLUMNS_ROWS\":\n for (let el of cmd.elements) {\n if (cmd.dimension === \"ROW\") {\n const height = this.getRowTallestCellSize(cmd.sheetId, el);\n const size = height;\n this.history.update(\"sizes\", cmd.sheetId, cmd.dimension, el, {\n manualSize: cmd.size || undefined,\n computedSize: lazy(size),\n });\n }\n else {\n this.history.update(\"sizes\", cmd.sheetId, cmd.dimension, el, {\n manualSize: cmd.size || undefined,\n computedSize: lazy(cmd.size || DEFAULT_CELL_WIDTH),\n });\n }\n }\n break;\n case \"UPDATE_CELL\":\n if (!((_c = (_b = (_a = this.sizes[cmd.sheetId]) === null || _a === void 0 ? void 0 : _a[\"ROW\"]) === null || _b === void 0 ? void 0 : _b[cmd.row]) === null || _c === void 0 ? void 0 : _c.manualSize)) {\n const { sheetId, row } = cmd;\n this.history.update(\"sizes\", sheetId, \"ROW\", row, \"computedSize\", lazy(() => this.getRowTallestCellSize(sheetId, row)));\n }\n break;\n case \"ADD_MERGE\":\n case \"REMOVE_MERGE\":\n for (let target of cmd.target) {\n for (let row of range(target.top, target.bottom + 1)) {\n const rowHeight = this.getRowTallestCellSize(cmd.sheetId, row);\n if (rowHeight !== this.getRowSize(cmd.sheetId, row)) {\n this.history.update(\"sizes\", cmd.sheetId, \"ROW\", row, \"computedSize\", lazy(rowHeight));\n }\n }\n }\n break;\n }\n return;\n }\n getColSize(sheetId, index) {\n return this.getHeaderSize(sheetId, \"COL\", index);\n }\n getRowSize(sheetId, index) {\n return this.getHeaderSize(sheetId, \"ROW\", index);\n }\n getHeaderSize(sheetId, dimension, index) {\n var _a, _b, _c, _d;\n return Math.round(((_b = (_a = this.sizes[sheetId]) === null || _a === void 0 ? void 0 : _a[dimension][index]) === null || _b === void 0 ? void 0 : _b.manualSize) ||\n ((_d = (_c = this.sizes[sheetId]) === null || _c === void 0 ? void 0 : _c[dimension][index]) === null || _d === void 0 ? void 0 : _d.computedSize()) ||\n this.getDefaultHeaderSize(dimension));\n }\n computeSheetSizes(sheetId) {\n var _a, _b;\n const sizes = { COL: [], ROW: [] };\n for (let col of range(0, this.getters.getNumberCols(sheetId))) {\n sizes.COL.push(this.getHeaderSize(sheetId, \"COL\", col));\n }\n for (let row of range(0, this.getters.getNumberRows(sheetId))) {\n let rowSize = (_b = (_a = this.sizes[sheetId]) === null || _a === void 0 ? void 0 : _a[\"ROW\"]) === null || _b === void 0 ? void 0 : _b[row].manualSize;\n if (!rowSize) {\n const height = this.getRowTallestCellSize(sheetId, row);\n rowSize = height;\n }\n sizes.ROW.push(rowSize);\n }\n return sizes;\n }\n getDefaultHeaderSize(dimension) {\n return dimension === \"COL\" ? DEFAULT_CELL_WIDTH : DEFAULT_CELL_HEIGHT;\n }\n /**\n * Return the height the cell should have in the sheet, which is either DEFAULT_CELL_HEIGHT if the cell is in a multi-row\n * merge, or the height of the cell computed based on its font size.\n */\n getCellHeight(position) {\n const merge = this.getters.getMerge(position);\n if (merge && merge.bottom !== merge.top) {\n return DEFAULT_CELL_HEIGHT;\n }\n const cell = this.getters.getCell(position);\n return getDefaultCellHeight(cell === null || cell === void 0 ? void 0 : cell.style);\n }\n /**\n * Get the tallest cell of a row and its size.\n *\n * The tallest cell of the row correspond to the cell with the biggest font size,\n * and that is not part of a multi-line merge.\n */\n getRowTallestCellSize(sheetId, row) {\n const cellIds = this.getters.getRowCells(sheetId, row);\n let maxHeight = 0;\n for (let i = 0; i < cellIds.length; i++) {\n const cell = this.getters.getCellById(cellIds[i]);\n if (!cell)\n continue;\n const position = this.getters.getCellPosition(cell.id);\n const cellHeight = this.getCellHeight(position);\n if (cellHeight > maxHeight && cellHeight > DEFAULT_CELL_HEIGHT) {\n maxHeight = cellHeight;\n }\n }\n if (maxHeight <= DEFAULT_CELL_HEIGHT) {\n return DEFAULT_CELL_HEIGHT;\n }\n return maxHeight;\n }\n import(data) {\n for (let sheet of data.sheets) {\n const manualSizes = { COL: [], ROW: [] };\n for (let [rowIndex, row] of Object.entries(sheet.rows)) {\n if (row.size) {\n manualSizes[\"ROW\"][rowIndex] = row.size;\n }\n }\n for (let [colIndex, col] of Object.entries(sheet.cols)) {\n if (col.size) {\n manualSizes[\"COL\"][colIndex] = col.size;\n }\n }\n const computedSizes = this.computeSheetSizes(sheet.id);\n this.sizes[sheet.id] = {\n COL: computedSizes.COL.map((size, i) => ({\n manualSize: manualSizes.COL[i],\n computedSize: lazy(size),\n })),\n ROW: computedSizes.ROW.map((size, i) => ({\n manualSize: manualSizes.ROW[i],\n computedSize: lazy(size),\n })),\n };\n }\n return;\n }\n exportForExcel(data) {\n this.exportData(data, true);\n }\n export(data) {\n this.exportData(data);\n }\n /**\n * Export the header sizes\n *\n * @param exportDefaults : if true, export column/row sizes even if they have the default size\n */\n exportData(data, exportDefaults = false) {\n var _a, _b;\n for (let sheet of data.sheets) {\n // Export row sizes\n if (sheet.rows === undefined) {\n sheet.rows = {};\n }\n for (let row of range(0, this.getters.getNumberRows(sheet.id))) {\n if (exportDefaults || ((_a = this.sizes[sheet.id][\"ROW\"][row]) === null || _a === void 0 ? void 0 : _a.manualSize)) {\n sheet.rows[row] = { ...sheet.rows[row], size: this.getRowSize(sheet.id, row) };\n }\n }\n // Export col sizes\n if (sheet.cols === undefined) {\n sheet.cols = {};\n }\n for (let col of range(0, this.getters.getNumberCols(sheet.id))) {\n if (exportDefaults || ((_b = this.sizes[sheet.id][\"COL\"][col]) === null || _b === void 0 ? void 0 : _b.manualSize)) {\n sheet.cols[col] = { ...sheet.cols[col], size: this.getColSize(sheet.id, col) };\n }\n }\n }\n }\n }\n HeaderSizePlugin.getters = [\"getRowSize\", \"getColSize\"];\n\n class HeaderVisibilityPlugin extends CorePlugin {\n constructor() {\n super(...arguments);\n this.hiddenHeaders = {};\n }\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"HIDE_COLUMNS_ROWS\": {\n if (!this.hiddenHeaders[cmd.sheetId]) {\n return 27 /* CommandResult.InvalidSheetId */;\n }\n const hiddenGroup = cmd.dimension === \"COL\"\n ? this.getHiddenColsGroups(cmd.sheetId)\n : this.getHiddenRowsGroups(cmd.sheetId);\n const elements = cmd.dimension === \"COL\"\n ? this.getters.getNumberCols(cmd.sheetId)\n : this.getters.getNumberRows(cmd.sheetId);\n return (hiddenGroup || []).flat().concat(cmd.elements).length < elements\n ? 0 /* CommandResult.Success */\n : 66 /* CommandResult.TooManyHiddenElements */;\n }\n }\n return 0 /* CommandResult.Success */;\n }\n handle(cmd) {\n switch (cmd.type) {\n case \"CREATE_SHEET\":\n const hiddenHeaders = {\n COL: Array(this.getters.getNumberCols(cmd.sheetId)).fill(false),\n ROW: Array(this.getters.getNumberRows(cmd.sheetId)).fill(false),\n };\n this.history.update(\"hiddenHeaders\", cmd.sheetId, hiddenHeaders);\n break;\n case \"DUPLICATE_SHEET\":\n this.history.update(\"hiddenHeaders\", cmd.sheetIdTo, deepCopy(this.hiddenHeaders[cmd.sheetId]));\n break;\n case \"DELETE_SHEET\":\n this.history.update(\"hiddenHeaders\", cmd.sheetId, undefined);\n break;\n case \"REMOVE_COLUMNS_ROWS\": {\n const hiddenHeaders = [...this.hiddenHeaders[cmd.sheetId][cmd.dimension]];\n for (let el of [...cmd.elements].sort((a, b) => b - a)) {\n hiddenHeaders.splice(el, 1);\n }\n this.history.update(\"hiddenHeaders\", cmd.sheetId, cmd.dimension, hiddenHeaders);\n break;\n }\n case \"ADD_COLUMNS_ROWS\": {\n const hiddenHeaders = [...this.hiddenHeaders[cmd.sheetId][cmd.dimension]];\n const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);\n hiddenHeaders.splice(addIndex, 0, ...Array(cmd.quantity).fill(false));\n this.history.update(\"hiddenHeaders\", cmd.sheetId, cmd.dimension, hiddenHeaders);\n break;\n }\n case \"HIDE_COLUMNS_ROWS\":\n for (let el of cmd.elements) {\n this.history.update(\"hiddenHeaders\", cmd.sheetId, cmd.dimension, el, true);\n }\n break;\n case \"UNHIDE_COLUMNS_ROWS\":\n for (let el of cmd.elements) {\n this.history.update(\"hiddenHeaders\", cmd.sheetId, cmd.dimension, el, false);\n }\n break;\n }\n return;\n }\n isRowHiddenByUser(sheetId, index) {\n return this.hiddenHeaders[sheetId].ROW[index];\n }\n isColHiddenByUser(sheetId, index) {\n return this.hiddenHeaders[sheetId].COL[index];\n }\n getHiddenColsGroups(sheetId) {\n const consecutiveIndexes = [[]];\n const hiddenCols = this.hiddenHeaders[sheetId].COL;\n for (let col = 0; col < hiddenCols.length; col++) {\n const isColHidden = hiddenCols[col];\n if (isColHidden) {\n consecutiveIndexes[consecutiveIndexes.length - 1].push(col);\n }\n else {\n if (consecutiveIndexes[consecutiveIndexes.length - 1].length !== 0) {\n consecutiveIndexes.push([]);\n }\n }\n }\n if (consecutiveIndexes[consecutiveIndexes.length - 1].length === 0) {\n consecutiveIndexes.pop();\n }\n return consecutiveIndexes;\n }\n getHiddenRowsGroups(sheetId) {\n const consecutiveIndexes = [[]];\n const hiddenCols = this.hiddenHeaders[sheetId].ROW;\n for (let row = 0; row < hiddenCols.length; row++) {\n const isRowHidden = hiddenCols[row];\n if (isRowHidden) {\n consecutiveIndexes[consecutiveIndexes.length - 1].push(row);\n }\n else {\n if (consecutiveIndexes[consecutiveIndexes.length - 1].length !== 0) {\n consecutiveIndexes.push([]);\n }\n }\n }\n if (consecutiveIndexes[consecutiveIndexes.length - 1].length === 0) {\n consecutiveIndexes.pop();\n }\n return consecutiveIndexes;\n }\n import(data) {\n var _a, _b;\n for (let sheet of data.sheets) {\n this.hiddenHeaders[sheet.id] = { COL: [], ROW: [] };\n for (let row = 0; row < sheet.rowNumber; row++) {\n this.hiddenHeaders[sheet.id].ROW[row] = Boolean((_a = sheet.rows[row]) === null || _a === void 0 ? void 0 : _a.isHidden);\n }\n for (let col = 0; col < sheet.colNumber; col++) {\n this.hiddenHeaders[sheet.id].COL[col] = Boolean((_b = sheet.cols[col]) === null || _b === void 0 ? void 0 : _b.isHidden);\n }\n }\n return;\n }\n exportForExcel(data) {\n this.exportData(data, true);\n }\n export(data) {\n this.exportData(data);\n }\n exportData(data, exportDefaults = false) {\n for (let sheet of data.sheets) {\n if (sheet.rows === undefined) {\n sheet.rows = {};\n }\n for (let row = 0; row < this.getters.getNumberRows(sheet.id); row++) {\n if (exportDefaults || this.hiddenHeaders[sheet.id][\"ROW\"][row]) {\n if (sheet.rows[row] === undefined) {\n sheet.rows[row] = {};\n }\n sheet.rows[row].isHidden = this.hiddenHeaders[sheet.id][\"ROW\"][row];\n }\n }\n if (sheet.cols === undefined) {\n sheet.cols = {};\n }\n for (let col = 0; col < this.getters.getNumberCols(sheet.id); col++) {\n if (exportDefaults || this.hiddenHeaders[sheet.id][\"COL\"][col]) {\n if (sheet.cols[col] === undefined) {\n sheet.cols[col] = {};\n }\n sheet.cols[col].isHidden = this.hiddenHeaders[sheet.id][\"COL\"][col];\n }\n }\n }\n }\n }\n HeaderVisibilityPlugin.getters = [\n \"getHiddenColsGroups\",\n \"getHiddenRowsGroups\",\n \"isRowHiddenByUser\",\n \"isColHiddenByUser\",\n ];\n\n class ImagePlugin extends CorePlugin {\n constructor(config) {\n super(config);\n this.images = {};\n /**\n * paths of images synced with the file store server.\n */\n this.syncedImages = new Set();\n this.fileStore = config.external.fileStore;\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"CREATE_IMAGE\":\n if (this.getters.getFigure(cmd.sheetId, cmd.figureId)) {\n return 28 /* CommandResult.InvalidFigureId */;\n }\n return 0 /* CommandResult.Success */;\n default:\n return 0 /* CommandResult.Success */;\n }\n }\n handle(cmd) {\n switch (cmd.type) {\n case \"CREATE_IMAGE\":\n this.addFigure(cmd.figureId, cmd.sheetId, cmd.position, cmd.size);\n this.history.update(\"images\", cmd.sheetId, cmd.figureId, cmd.definition);\n this.syncedImages.add(cmd.definition.path);\n break;\n case \"DUPLICATE_SHEET\": {\n const sheetFiguresFrom = this.getters.getFigures(cmd.sheetId);\n for (const fig of sheetFiguresFrom) {\n if (fig.tag === \"image\") {\n const figureIdBase = fig.id.split(FIGURE_ID_SPLITTER).pop();\n const duplicatedFigureId = `${cmd.sheetIdTo}${FIGURE_ID_SPLITTER}${figureIdBase}`;\n const image = this.getImage(fig.id);\n if (image) {\n const size = { width: fig.width, height: fig.height };\n this.dispatch(\"CREATE_IMAGE\", {\n sheetId: cmd.sheetIdTo,\n figureId: duplicatedFigureId,\n position: { x: fig.x, y: fig.y },\n size,\n definition: deepCopy(image),\n });\n }\n }\n }\n break;\n }\n case \"DELETE_FIGURE\":\n this.history.update(\"images\", cmd.sheetId, cmd.id, undefined);\n break;\n case \"DELETE_SHEET\":\n this.history.update(\"images\", cmd.sheetId, undefined);\n break;\n }\n }\n /**\n * Delete unused images from the file store\n */\n garbageCollectExternalResources() {\n var _a;\n const images = new Set(this.getAllImages().map((image) => image.path));\n for (const path of this.syncedImages) {\n if (!images.has(path)) {\n (_a = this.fileStore) === null || _a === void 0 ? void 0 : _a.delete(path);\n }\n }\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n getImage(figureId) {\n for (const sheet of Object.values(this.images)) {\n if (sheet && sheet[figureId]) {\n return sheet[figureId];\n }\n }\n throw new Error(`There is no image with the given figureId: ${figureId}`);\n }\n getImagePath(figureId) {\n return this.getImage(figureId).path;\n }\n getImageSize(figureId) {\n return this.getImage(figureId).size;\n }\n // ---------------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------------\n addFigure(id, sheetId, position, size) {\n const figure = {\n id,\n x: position.x,\n y: position.y,\n width: size.width,\n height: size.height,\n tag: \"image\",\n };\n this.dispatch(\"CREATE_FIGURE\", { sheetId, figure });\n }\n import(data) {\n for (const sheet of data.sheets) {\n const images = (sheet.figures || []).filter((figure) => figure.tag === \"image\");\n for (const image of images) {\n this.history.update(\"images\", sheet.id, image.id, image.data);\n this.syncedImages.add(image.data.path);\n }\n }\n }\n export(data) {\n var _a;\n for (const sheet of data.sheets) {\n const images = sheet.figures.filter((figure) => figure.tag === \"image\");\n for (const image of images) {\n image.data = (_a = this.images[sheet.id]) === null || _a === void 0 ? void 0 : _a[image.id];\n }\n }\n }\n getAllImages() {\n const images = [];\n for (const sheetId in this.images) {\n images.push(...Object.values(this.images[sheetId] || {}).filter(isDefined$1));\n }\n return images;\n }\n }\n ImagePlugin.getters = [\"getImage\", \"getImagePath\", \"getImageSize\"];\n\n class MergePlugin extends CorePlugin {\n constructor() {\n super(...arguments);\n this.nextId = 1;\n this.merges = {};\n this.mergeCellMap = {};\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n allowDispatch(cmd) {\n const force = \"force\" in cmd ? !!cmd.force : false;\n switch (cmd.type) {\n case \"ADD_MERGE\":\n if (force) {\n return this.checkValidations(cmd, this.checkFrozenPanes);\n }\n return this.checkValidations(cmd, this.checkDestructiveMerge, this.checkOverlap, this.checkFrozenPanes);\n case \"UPDATE_CELL\":\n return this.checkMergedContentUpdate(cmd);\n case \"REMOVE_MERGE\":\n return this.checkMergeExists(cmd);\n default:\n return 0 /* CommandResult.Success */;\n }\n }\n handle(cmd) {\n switch (cmd.type) {\n case \"CREATE_SHEET\":\n this.history.update(\"merges\", cmd.sheetId, {});\n this.history.update(\"mergeCellMap\", cmd.sheetId, {});\n break;\n case \"DELETE_SHEET\":\n this.history.update(\"merges\", cmd.sheetId, {});\n this.history.update(\"mergeCellMap\", cmd.sheetId, {});\n break;\n case \"DUPLICATE_SHEET\":\n const merges = this.merges[cmd.sheetId];\n if (!merges)\n break;\n for (const range of Object.values(merges).filter(isDefined$1)) {\n this.addMerge(cmd.sheetIdTo, range.zone);\n }\n break;\n case \"ADD_MERGE\":\n for (const zone of cmd.target) {\n this.addMerge(cmd.sheetId, zone);\n }\n break;\n case \"REMOVE_MERGE\":\n for (const zone of cmd.target) {\n this.removeMerge(cmd.sheetId, zone);\n }\n break;\n }\n }\n adaptRanges(applyChange, sheetId) {\n const sheetIds = sheetId ? [sheetId] : Object.keys(this.merges);\n for (const sheetId of sheetIds) {\n this.applyRangeChangeOnSheet(sheetId, applyChange);\n }\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n getMerges(sheetId) {\n return Object.keys(this.merges[sheetId] || {})\n .map((mergeId) => this.getMergeById(sheetId, parseInt(mergeId, 10)))\n .filter(isDefined$1);\n }\n getMerge({ sheetId, col, row }) {\n var _a;\n const sheetMap = this.mergeCellMap[sheetId];\n const mergeId = sheetMap ? col in sheetMap && ((_a = sheetMap[col]) === null || _a === void 0 ? void 0 : _a[row]) : undefined;\n return mergeId ? this.getMergeById(sheetId, mergeId) : undefined;\n }\n getMergesInZone(sheetId, zone) {\n var _a;\n const sheetMap = this.mergeCellMap[sheetId];\n if (!sheetMap)\n return [];\n const mergeIds = new Set();\n for (const { col, row } of positions(zone)) {\n const mergeId = (_a = sheetMap[col]) === null || _a === void 0 ? void 0 : _a[row];\n if (mergeId) {\n mergeIds.add(mergeId);\n }\n }\n return Array.from(mergeIds)\n .map((mergeId) => this.getMergeById(sheetId, mergeId))\n .filter(isDefined$1);\n }\n /**\n * Return true if the zone intersects an existing merge:\n * if they have at least a common cell\n */\n doesIntersectMerge(sheetId, zone) {\n return positions(zone).some(({ col, row }) => this.getMerge({ sheetId, col, row }) !== undefined);\n }\n /**\n * Returns true if two columns have at least one merge in common\n */\n doesColumnsHaveCommonMerges(sheetId, colA, colB) {\n const sheet = this.getters.getSheet(sheetId);\n for (let row = 0; row < this.getters.getNumberRows(sheetId); row++) {\n if (this.isInSameMerge(sheet.id, colA, row, colB, row)) {\n return true;\n }\n }\n return false;\n }\n /**\n * Returns true if two rows have at least one merge in common\n */\n doesRowsHaveCommonMerges(sheetId, rowA, rowB) {\n const sheet = this.getters.getSheet(sheetId);\n for (let col = 0; col <= this.getters.getNumberCols(sheetId); col++) {\n if (this.isInSameMerge(sheet.id, col, rowA, col, rowB)) {\n return true;\n }\n }\n return false;\n }\n /**\n * Add all necessary merge to the current selection to make it valid\n */\n expandZone(sheetId, zone) {\n let { left, right, top, bottom } = zone;\n let result = { left, right, top, bottom };\n for (let id in this.merges[sheetId]) {\n const merge = this.getMergeById(sheetId, parseInt(id));\n if (merge && overlap(merge, result)) {\n result = union(merge, result);\n }\n }\n return isEqual(result, zone) ? result : this.expandZone(sheetId, result);\n }\n isInSameMerge(sheetId, colA, rowA, colB, rowB) {\n const mergeA = this.getMerge({ sheetId, col: colA, row: rowA });\n const mergeB = this.getMerge({ sheetId, col: colB, row: rowB });\n if (!mergeA || !mergeB) {\n return false;\n }\n return isEqual(mergeA, mergeB);\n }\n isInMerge({ sheetId, col, row }) {\n var _a;\n const sheetMap = this.mergeCellMap[sheetId];\n return sheetMap ? col in sheetMap && Boolean((_a = sheetMap[col]) === null || _a === void 0 ? void 0 : _a[row]) : false;\n }\n getMainCellPosition(position) {\n if (!this.isInMerge(position)) {\n return position;\n }\n const mergeTopLeftPos = this.getMerge(position).topLeft;\n return { sheetId: position.sheetId, col: mergeTopLeftPos.col, row: mergeTopLeftPos.row };\n }\n getBottomLeftCell(position) {\n if (!this.isInMerge(position)) {\n return position;\n }\n const { bottom, left } = this.getMerge(position);\n return { sheetId: position.sheetId, col: left, row: bottom };\n }\n isMergeHidden(sheetId, merge) {\n const hiddenColsGroups = this.getters.getHiddenColsGroups(sheetId);\n const hiddenRowsGroups = this.getters.getHiddenRowsGroups(sheetId);\n for (let group of hiddenColsGroups) {\n if (merge.left >= group[0] && merge.right <= group[group.length - 1]) {\n return true;\n }\n }\n for (let group of hiddenRowsGroups) {\n if (merge.top >= group[0] && merge.bottom <= group[group.length - 1]) {\n return true;\n }\n }\n return false;\n }\n /**\n * Check if the zone represents a single cell or a single merge.\n */\n isSingleCellOrMerge(sheetId, zone) {\n const merge = this.getMerge({ sheetId, col: zone.left, row: zone.top });\n if (merge) {\n return isEqual(zone, merge);\n }\n const { width, height } = zoneToDimension(zone);\n return width === 1 && height === 1;\n }\n // ---------------------------------------------------------------------------\n // Merges\n // ---------------------------------------------------------------------------\n /**\n * Return true if the current selection requires losing state if it is merged.\n * This happens when there is some textual content in other cells than the\n * top left.\n */\n isMergeDestructive(sheetId, zone) {\n let { left, right, top, bottom } = zone;\n right = clip(right, 0, this.getters.getNumberCols(sheetId) - 1);\n bottom = clip(bottom, 0, this.getters.getNumberRows(sheetId) - 1);\n for (let row = top; row <= bottom; row++) {\n for (let col = left; col <= right; col++) {\n if (col !== left || row !== top) {\n const cell = this.getters.getCell({ sheetId, col, row });\n if (cell && cell.content !== \"\") {\n return true;\n }\n }\n }\n }\n return false;\n }\n getMergeById(sheetId, mergeId) {\n var _a;\n const range = (_a = this.merges[sheetId]) === null || _a === void 0 ? void 0 : _a[mergeId];\n return range !== undefined ? rangeToMerge(mergeId, range) : undefined;\n }\n checkDestructiveMerge({ sheetId, target }) {\n const sheet = this.getters.tryGetSheet(sheetId);\n if (!sheet)\n return 0 /* CommandResult.Success */;\n const isDestructive = target.some((zone) => this.isMergeDestructive(sheetId, zone));\n return isDestructive ? 3 /* CommandResult.MergeIsDestructive */ : 0 /* CommandResult.Success */;\n }\n checkOverlap({ target }) {\n for (const zone of target) {\n for (const zone2 of target) {\n if (zone !== zone2 && overlap(zone, zone2)) {\n return 65 /* CommandResult.MergeOverlap */;\n }\n }\n }\n return 0 /* CommandResult.Success */;\n }\n checkFrozenPanes({ sheetId, target }) {\n const sheet = this.getters.tryGetSheet(sheetId);\n if (!sheet)\n return 0 /* CommandResult.Success */;\n const { xSplit, ySplit } = this.getters.getPaneDivisions(sheetId);\n for (const zone of target) {\n if ((zone.left < xSplit && zone.right >= xSplit) ||\n (zone.top < ySplit && zone.bottom >= ySplit)) {\n return 75 /* CommandResult.FrozenPaneOverlap */;\n }\n }\n return 0 /* CommandResult.Success */;\n }\n /**\n * The content of a merged cell should always be empty.\n * Except for the top-left cell.\n */\n checkMergedContentUpdate(cmd) {\n const { col, row, content } = cmd;\n if (content === undefined) {\n return 0 /* CommandResult.Success */;\n }\n const { col: mainCol, row: mainRow } = this.getMainCellPosition(cmd);\n if (mainCol === col && mainRow === row) {\n return 0 /* CommandResult.Success */;\n }\n return 4 /* CommandResult.CellIsMerged */;\n }\n checkMergeExists(cmd) {\n const { sheetId, target } = cmd;\n for (const zone of target) {\n const { left, top } = zone;\n const merge = this.getMerge({ sheetId, col: left, row: top });\n if (merge === undefined || !isEqual(zone, merge)) {\n return 5 /* CommandResult.InvalidTarget */;\n }\n }\n return 0 /* CommandResult.Success */;\n }\n /**\n * Merge the current selection. Note that:\n * - it assumes that we have a valid selection (no intersection with other\n * merges)\n * - it does nothing if the merge is trivial: A1:A1\n */\n addMerge(sheetId, zone) {\n let { left, right, top, bottom } = zone;\n right = clip(right, 0, this.getters.getNumberCols(sheetId) - 1);\n bottom = clip(bottom, 0, this.getters.getNumberRows(sheetId) - 1);\n const tl = toXC(left, top);\n const br = toXC(right, bottom);\n if (tl === br) {\n return;\n }\n const topLeft = this.getters.getCell({ sheetId, col: left, row: top });\n let id = this.nextId++;\n this.history.update(\"merges\", sheetId, id, this.getters.getRangeFromSheetXC(sheetId, zoneToXc({ left, top, right, bottom })));\n let previousMerges = new Set();\n for (let row = top; row <= bottom; row++) {\n for (let col = left; col <= right; col++) {\n if (col !== left || row !== top) {\n this.dispatch(\"UPDATE_CELL\", {\n sheetId,\n col,\n row,\n style: topLeft ? topLeft.style : null,\n content: \"\",\n });\n }\n const merge = this.getMerge({ sheetId, col, row });\n if (merge) {\n previousMerges.add(merge.id);\n }\n this.history.update(\"mergeCellMap\", sheetId, col, row, id);\n }\n }\n for (let mergeId of previousMerges) {\n const { top, bottom, left, right } = this.getMergeById(sheetId, mergeId);\n for (let row = top; row <= bottom; row++) {\n for (let col = left; col <= right; col++) {\n const position = { sheetId, col, row };\n const merge = this.getMerge(position);\n if (!merge || merge.id !== id) {\n this.history.update(\"mergeCellMap\", sheetId, col, row, undefined);\n this.dispatch(\"CLEAR_CELL\", position);\n }\n }\n }\n this.history.update(\"merges\", sheetId, mergeId, undefined);\n }\n }\n removeMerge(sheetId, zone) {\n const { left, top, bottom, right } = zone;\n const merge = this.getMerge({ sheetId, col: left, row: top });\n if (merge === undefined || !isEqual(zone, merge)) {\n return;\n }\n this.history.update(\"merges\", sheetId, merge.id, undefined);\n for (let r = top; r <= bottom; r++) {\n for (let c = left; c <= right; c++) {\n this.history.update(\"mergeCellMap\", sheetId, c, r, undefined);\n }\n }\n }\n /**\n * Apply a range change on merges of a particular sheet.\n */\n applyRangeChangeOnSheet(sheetId, applyChange) {\n const merges = Object.entries(this.merges[sheetId] || {});\n for (const [mergeId, range] of merges) {\n if (range) {\n const currentZone = range.zone;\n const result = applyChange(range);\n switch (result.changeType) {\n case \"NONE\":\n break;\n case \"REMOVE\":\n this.removeMerge(sheetId, currentZone);\n break;\n default:\n const { width, height } = zoneToDimension(result.range.zone);\n if (width === 1 && height === 1) {\n this.removeMerge(sheetId, currentZone);\n }\n else {\n this.history.update(\"merges\", sheetId, parseInt(mergeId, 10), result.range);\n }\n break;\n }\n }\n }\n this.history.update(\"mergeCellMap\", sheetId, {});\n for (const merge of this.getMerges(sheetId)) {\n for (const { col, row } of positions(merge)) {\n this.history.update(\"mergeCellMap\", sheetId, col, row, merge.id);\n }\n }\n }\n // ---------------------------------------------------------------------------\n // Import/Export\n // ---------------------------------------------------------------------------\n import(data) {\n const sheets = data.sheets || [];\n for (let sheetData of sheets) {\n this.history.update(\"merges\", sheetData.id, {});\n this.history.update(\"mergeCellMap\", sheetData.id, {});\n if (sheetData.merges) {\n this.importMerges(sheetData.id, sheetData.merges);\n }\n }\n }\n importMerges(sheetId, merges) {\n for (let merge of merges) {\n this.addMerge(sheetId, toZone(merge));\n }\n }\n export(data) {\n for (let sheetData of data.sheets) {\n const merges = this.merges[sheetData.id];\n if (merges) {\n sheetData.merges.push(...exportMerges(merges));\n }\n }\n }\n exportForExcel(data) {\n this.export(data);\n }\n }\n MergePlugin.getters = [\n \"isInMerge\",\n \"isInSameMerge\",\n \"isMergeHidden\",\n \"getMainCellPosition\",\n \"getBottomLeftCell\",\n \"expandZone\",\n \"doesIntersectMerge\",\n \"doesColumnsHaveCommonMerges\",\n \"doesRowsHaveCommonMerges\",\n \"getMerges\",\n \"getMerge\",\n \"getMergesInZone\",\n \"isSingleCellOrMerge\",\n ];\n function exportMerges(merges) {\n return Object.entries(merges)\n .map(([mergeId, range]) => (range ? rangeToMerge(parseInt(mergeId, 10), range) : undefined))\n .filter(isDefined$1)\n .map((merge) => toXC(merge.left, merge.top) + \":\" + toXC(merge.right, merge.bottom));\n }\n function rangeToMerge(mergeId, range) {\n return {\n ...range.zone,\n topLeft: { col: range.zone.left, row: range.zone.top },\n id: mergeId,\n };\n }\n\n class RangeAdapter {\n constructor(getters) {\n this.providers = [];\n this.getters = getters;\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n allowDispatch(cmd) {\n if (cmd.type === \"MOVE_RANGES\") {\n return cmd.target.length === 1 ? 0 /* CommandResult.Success */ : 26 /* CommandResult.InvalidZones */;\n }\n return 0 /* CommandResult.Success */;\n }\n beforeHandle(command) { }\n handle(cmd) {\n switch (cmd.type) {\n case \"REMOVE_COLUMNS_ROWS\": {\n let start = cmd.dimension === \"COL\" ? \"left\" : \"top\";\n let end = cmd.dimension === \"COL\" ? \"right\" : \"bottom\";\n let dimension = cmd.dimension === \"COL\" ? \"columns\" : \"rows\";\n const elements = [...cmd.elements];\n elements.sort((a, b) => b - a);\n const groups = groupConsecutive(elements);\n this.executeOnAllRanges((range) => {\n if (range.sheetId !== cmd.sheetId) {\n return { changeType: \"NONE\" };\n }\n let newRange = range;\n let changeType = \"NONE\";\n for (let group of groups) {\n const min = Math.min(...group);\n const max = Math.max(...group);\n if (range.zone[start] <= min && min <= range.zone[end]) {\n const toRemove = Math.min(range.zone[end], max) - min + 1;\n changeType = \"RESIZE\";\n newRange = this.createAdaptedRange(newRange, dimension, changeType, -toRemove);\n }\n else if (range.zone[start] >= min && range.zone[end] <= max) {\n changeType = \"REMOVE\";\n newRange = range.clone({ ...this.getInvalidRange() });\n }\n else if (range.zone[start] <= max && range.zone[end] >= max) {\n const toRemove = max - range.zone[start] + 1;\n changeType = \"RESIZE\";\n newRange = this.createAdaptedRange(newRange, dimension, changeType, -toRemove);\n newRange = this.createAdaptedRange(newRange, dimension, \"MOVE\", -(range.zone[start] - min));\n }\n else if (min < range.zone[start]) {\n changeType = \"MOVE\";\n newRange = this.createAdaptedRange(newRange, dimension, changeType, -(max - min + 1));\n }\n }\n if (changeType !== \"NONE\") {\n return { changeType, range: newRange };\n }\n return { changeType: \"NONE\" };\n }, cmd.sheetId);\n break;\n }\n case \"ADD_COLUMNS_ROWS\": {\n let start = cmd.dimension === \"COL\" ? \"left\" : \"top\";\n let end = cmd.dimension === \"COL\" ? \"right\" : \"bottom\";\n let dimension = cmd.dimension === \"COL\" ? \"columns\" : \"rows\";\n this.executeOnAllRanges((range) => {\n if (range.sheetId !== cmd.sheetId) {\n return { changeType: \"NONE\" };\n }\n if (cmd.position === \"after\") {\n if (range.zone[start] <= cmd.base && cmd.base < range.zone[end]) {\n return {\n changeType: \"RESIZE\",\n range: this.createAdaptedRange(range, dimension, \"RESIZE\", cmd.quantity),\n };\n }\n if (cmd.base < range.zone[start]) {\n return {\n changeType: \"MOVE\",\n range: this.createAdaptedRange(range, dimension, \"MOVE\", cmd.quantity),\n };\n }\n }\n else {\n if (range.zone[start] < cmd.base && cmd.base <= range.zone[end]) {\n return {\n changeType: \"RESIZE\",\n range: this.createAdaptedRange(range, dimension, \"RESIZE\", cmd.quantity),\n };\n }\n if (cmd.base <= range.zone[start]) {\n return {\n changeType: \"MOVE\",\n range: this.createAdaptedRange(range, dimension, \"MOVE\", cmd.quantity),\n };\n }\n }\n return { changeType: \"NONE\" };\n }, cmd.sheetId);\n break;\n }\n case \"DELETE_SHEET\": {\n this.executeOnAllRanges((range) => {\n if (range.sheetId !== cmd.sheetId) {\n return { changeType: \"NONE\" };\n }\n const invalidSheetName = this.getters.getSheetName(cmd.sheetId);\n range = range.clone({\n ...this.getInvalidRange(),\n invalidSheetName,\n });\n return { changeType: \"REMOVE\", range };\n }, cmd.sheetId);\n break;\n }\n case \"RENAME_SHEET\": {\n this.executeOnAllRanges((range) => {\n if (range.sheetId === cmd.sheetId) {\n return { changeType: \"CHANGE\", range };\n }\n if (cmd.name && range.invalidSheetName === cmd.name) {\n const invalidSheetName = undefined;\n const sheetId = cmd.sheetId;\n const newRange = range.clone({ sheetId, invalidSheetName });\n return { changeType: \"CHANGE\", range: newRange };\n }\n return { changeType: \"NONE\" };\n });\n break;\n }\n case \"MOVE_RANGES\": {\n const originZone = cmd.target[0];\n this.executeOnAllRanges((range) => {\n if (range.sheetId !== cmd.sheetId || !isZoneInside(range.zone, originZone)) {\n return { changeType: \"NONE\" };\n }\n const targetSheetId = cmd.targetSheetId;\n const offsetX = cmd.col - originZone.left;\n const offsetY = cmd.row - originZone.top;\n const adaptedRange = this.createAdaptedRange(range, \"both\", \"MOVE\", [offsetX, offsetY]);\n const prefixSheet = cmd.sheetId === targetSheetId ? adaptedRange.prefixSheet : true;\n return {\n changeType: \"MOVE\",\n range: adaptedRange.clone({ sheetId: targetSheetId, prefixSheet }),\n };\n });\n break;\n }\n }\n }\n finalize() { }\n /**\n * Return a modified adapting function that verifies that after adapting a range, the range is still valid.\n * Any range that gets adapted by the function adaptRange in parameter does so\n * without caring if the start and end of the range in both row and column\n * direction can be incorrect. This function ensure that an incorrect range gets removed.\n */\n verifyRangeRemoved(adaptRange) {\n return (range) => {\n const result = adaptRange(range);\n if (result.changeType !== \"NONE\" && !isZoneValid(result.range.zone)) {\n return { range: result.range, changeType: \"REMOVE\" };\n }\n return result;\n };\n }\n createAdaptedRange(range, dimension, operation, by) {\n const zone = createAdaptedZone(range.unboundedZone, dimension, operation, by);\n const adaptedRange = range.clone({ zone });\n return adaptedRange;\n }\n executeOnAllRanges(adaptRange, sheetId) {\n const func = this.verifyRangeRemoved(adaptRange);\n for (const provider of this.providers) {\n provider(func, sheetId);\n }\n }\n /**\n * Stores the functions bound to each plugin to be able to iterate over all ranges of the application,\n * without knowing any details of the internal data structure of the plugins and without storing ranges\n * in the range adapter.\n *\n * @param provider a function bound to a plugin that will loop over its internal data structure to find\n * all ranges\n */\n addRangeProvider(provider) {\n this.providers.push(provider);\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n createAdaptedRanges(ranges, offsetX, offsetY, sheetId) {\n const rangesImpl = ranges.map((range) => RangeImpl.fromRange(range, this.getters));\n return rangesImpl.map((range) => {\n if (!isZoneValid(range.zone)) {\n return range;\n }\n const copySheetId = range.prefixSheet ? range.sheetId : sheetId;\n const unboundZone = {\n ...range.unboundedZone,\n // Don't shift left if the range is a full row without header\n left: range.isFullRow && !range.unboundedZone.hasHeader\n ? range.unboundedZone.left\n : range.unboundedZone.left + (range.parts[0].colFixed ? 0 : offsetX),\n // Don't shift right if the range is a full row\n right: range.isFullRow\n ? range.unboundedZone.right\n : range.unboundedZone.right +\n ((range.parts[1] || range.parts[0]).colFixed ? 0 : offsetX),\n // Don't shift up if the range is a column row without header\n top: range.isFullCol && !range.unboundedZone.hasHeader\n ? range.unboundedZone.top\n : range.unboundedZone.top + (range.parts[0].rowFixed ? 0 : offsetY),\n // Don't shift down if the range is a full column\n bottom: range.isFullCol\n ? range.unboundedZone.bottom\n : range.unboundedZone.bottom +\n ((range.parts[1] || range.parts[0]).rowFixed ? 0 : offsetY),\n };\n return range.clone({ sheetId: copySheetId, zone: unboundZone }).orderZone();\n });\n }\n /**\n * Creates a range from a XC reference that can contain a sheet reference\n * @param defaultSheetId the sheet to default to if the sheetXC parameter does not contain a sheet reference (usually the active sheet Id)\n * @param sheetXC the string description of a range, in the form SheetName!XC:XC\n */\n getRangeFromSheetXC(defaultSheetId, sheetXC) {\n if (!rangeReference.test(sheetXC)) {\n return new RangeImpl({\n sheetId: \"\",\n zone: { left: -1, top: -1, right: -1, bottom: -1 },\n parts: [],\n invalidXc: sheetXC,\n prefixSheet: false,\n }, this.getters.getSheetSize);\n }\n let sheetName;\n let xc = sheetXC;\n let prefixSheet = false;\n if (sheetXC.includes(\"!\")) {\n ({ xc, sheetName } = splitReference(sheetXC));\n if (sheetName) {\n prefixSheet = true;\n }\n }\n const zone = toUnboundedZone(xc);\n const parts = RangeImpl.getRangeParts(xc, zone);\n const invalidSheetName = sheetName && !this.getters.getSheetIdByName(sheetName) ? sheetName : undefined;\n const sheetId = this.getters.getSheetIdByName(sheetName) || defaultSheetId;\n const rangeInterface = { prefixSheet, zone, sheetId, invalidSheetName, parts };\n return new RangeImpl(rangeInterface, this.getters.getSheetSize).orderZone();\n }\n /**\n * Same as `getRangeString` but add all necessary merge to the range to make it a valid selection\n */\n getSelectionRangeString(range, forSheetId) {\n const rangeImpl = RangeImpl.fromRange(range, this.getters);\n const expandedZone = this.getters.expandZone(rangeImpl.sheetId, rangeImpl.zone);\n const expandedRange = rangeImpl.clone({\n zone: {\n ...expandedZone,\n bottom: rangeImpl.isFullCol ? undefined : expandedZone.bottom,\n right: rangeImpl.isFullRow ? undefined : expandedZone.right,\n },\n });\n return this.getRangeString(expandedRange, forSheetId);\n }\n /**\n * Gets the string that represents the range as it is at the moment of the call.\n * The string will be prefixed with the sheet name if the call specified a sheet id in `forSheetId`\n * different than the sheet on which the range has been created.\n *\n * @param range the range (received from getRangeFromXC or getRangeFromZone)\n * @param forSheetId the id of the sheet where the range string is supposed to be used.\n */\n getRangeString(range, forSheetId) {\n if (!range) {\n return INCORRECT_RANGE_STRING;\n }\n if (range.invalidXc) {\n return range.invalidXc;\n }\n if (range.zone.bottom - range.zone.top < 0 || range.zone.right - range.zone.left < 0) {\n return INCORRECT_RANGE_STRING;\n }\n if (range.zone.left < 0 || range.zone.top < 0) {\n return INCORRECT_RANGE_STRING;\n }\n const rangeImpl = RangeImpl.fromRange(range, this.getters);\n let prefixSheet = rangeImpl.sheetId !== forSheetId || rangeImpl.invalidSheetName || rangeImpl.prefixSheet;\n let sheetName = \"\";\n if (prefixSheet) {\n if (rangeImpl.invalidSheetName) {\n sheetName = rangeImpl.invalidSheetName;\n }\n else {\n sheetName = getComposerSheetName(this.getters.getSheetName(rangeImpl.sheetId));\n }\n }\n if (prefixSheet && !sheetName) {\n return INCORRECT_RANGE_STRING;\n }\n let rangeString = this.getRangePartString(rangeImpl, 0);\n if (rangeImpl.parts && rangeImpl.parts.length === 2) {\n // this if converts A2:A2 into A2 except if any part of the original range had fixed row or column (with $)\n if (rangeImpl.zone.top !== rangeImpl.zone.bottom ||\n rangeImpl.zone.left !== rangeImpl.zone.right ||\n rangeImpl.parts[0].rowFixed ||\n rangeImpl.parts[0].colFixed ||\n rangeImpl.parts[1].rowFixed ||\n rangeImpl.parts[1].colFixed) {\n rangeString += \":\";\n rangeString += this.getRangePartString(rangeImpl, 1);\n }\n }\n return `${prefixSheet ? sheetName + \"!\" : \"\"}${rangeString}`;\n }\n getRangeDataFromXc(sheetId, xc) {\n return this.getters.getRangeFromSheetXC(sheetId, xc).rangeData;\n }\n getRangeDataFromZone(sheetId, zone) {\n return { _sheetId: sheetId, _zone: zone };\n }\n getRangeFromRangeData(data) {\n const rangeInterface = {\n prefixSheet: false,\n zone: data._zone,\n sheetId: data._sheetId,\n invalidSheetName: undefined,\n parts: [\n { colFixed: false, rowFixed: false },\n { colFixed: false, rowFixed: false },\n ],\n };\n return new RangeImpl(rangeInterface, this.getters.getSheetSize);\n }\n // ---------------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------------\n /**\n * Get a Xc string that represent a part of a range\n */\n getRangePartString(range, part) {\n const colFixed = range.parts && range.parts[part].colFixed ? \"$\" : \"\";\n const col = part === 0 ? numberToLetters(range.zone.left) : numberToLetters(range.zone.right);\n const rowFixed = range.parts && range.parts[part].rowFixed ? \"$\" : \"\";\n const row = part === 0 ? String(range.zone.top + 1) : String(range.zone.bottom + 1);\n let str = \"\";\n if (range.isFullCol) {\n if (part === 0 && range.unboundedZone.hasHeader) {\n str = colFixed + col + rowFixed + row;\n }\n else {\n str = colFixed + col;\n }\n }\n else if (range.isFullRow) {\n if (part === 0 && range.unboundedZone.hasHeader) {\n str = colFixed + col + rowFixed + row;\n }\n else {\n str = rowFixed + row;\n }\n }\n else {\n str = colFixed + col + rowFixed + row;\n }\n return str;\n }\n getInvalidRange() {\n return {\n parts: [],\n prefixSheet: false,\n zone: { left: -1, top: -1, right: -1, bottom: -1 },\n sheetId: \"\",\n invalidXc: INCORRECT_RANGE_STRING,\n };\n }\n }\n RangeAdapter.getters = [\n \"getRangeString\",\n \"getSelectionRangeString\",\n \"getRangeFromSheetXC\",\n \"createAdaptedRanges\",\n \"getRangeDataFromXc\",\n \"getRangeDataFromZone\",\n \"getRangeFromRangeData\",\n ];\n\n class SheetPlugin extends CorePlugin {\n constructor() {\n super(...arguments);\n this.sheetIdsMapName = {};\n this.orderedSheetIds = [];\n this.sheets = {};\n this.cellPosition = {};\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n allowDispatch(cmd) {\n const genericChecks = this.chainValidations(this.checkSheetExists, this.checkZones)(cmd);\n if (genericChecks !== 0 /* CommandResult.Success */) {\n return genericChecks;\n }\n switch (cmd.type) {\n case \"HIDE_SHEET\": {\n if (this.getVisibleSheetIds().length === 1) {\n return 9 /* CommandResult.NotEnoughSheets */;\n }\n return 0 /* CommandResult.Success */;\n }\n case \"CREATE_SHEET\": {\n return this.checkValidations(cmd, this.checkSheetName, this.checkSheetPosition);\n }\n case \"MOVE_SHEET\":\n const currentIndex = this.orderedSheetIds.indexOf(cmd.sheetId);\n if (cmd.direction === \"left\") {\n const leftSheets = this.orderedSheetIds\n .slice(0, currentIndex)\n .map((id) => !this.isSheetVisible(id));\n return leftSheets.every((isHidden) => isHidden)\n ? 14 /* CommandResult.WrongSheetMove */\n : 0 /* CommandResult.Success */;\n }\n else {\n const rightSheets = this.orderedSheetIds\n .slice(currentIndex + 1)\n .map((id) => !this.isSheetVisible(id));\n return rightSheets.every((isHidden) => isHidden)\n ? 14 /* CommandResult.WrongSheetMove */\n : 0 /* CommandResult.Success */;\n }\n case \"RENAME_SHEET\":\n return this.isRenameAllowed(cmd);\n case \"DELETE_SHEET\":\n return this.orderedSheetIds.length > 1\n ? 0 /* CommandResult.Success */\n : 9 /* CommandResult.NotEnoughSheets */;\n case \"REMOVE_COLUMNS_ROWS\": {\n const length = cmd.dimension === \"COL\"\n ? this.getNumberCols(cmd.sheetId)\n : this.getNumberRows(cmd.sheetId);\n return length > cmd.elements.length\n ? 0 /* CommandResult.Success */\n : 8 /* CommandResult.NotEnoughElements */;\n }\n case \"FREEZE_ROWS\": {\n return this.checkValidations(cmd, this.checkRowFreezeQuantity, this.checkRowFreezeOverlapMerge);\n }\n case \"FREEZE_COLUMNS\": {\n return this.checkValidations(cmd, this.checkColFreezeQuantity, this.checkColFreezeOverlapMerge);\n }\n default:\n return 0 /* CommandResult.Success */;\n }\n }\n handle(cmd) {\n switch (cmd.type) {\n case \"SET_GRID_LINES_VISIBILITY\":\n this.setGridLinesVisibility(cmd.sheetId, cmd.areGridLinesVisible);\n break;\n case \"DELETE_CONTENT\":\n this.clearZones(cmd.sheetId, cmd.target);\n break;\n case \"CREATE_SHEET\":\n const sheet = this.createSheet(cmd.sheetId, cmd.name || this.getNextSheetName(), cmd.cols || 26, cmd.rows || 100, cmd.position);\n this.history.update(\"sheetIdsMapName\", sheet.name, sheet.id);\n break;\n case \"MOVE_SHEET\":\n this.moveSheet(cmd.sheetId, cmd.direction);\n break;\n case \"RENAME_SHEET\":\n this.renameSheet(this.sheets[cmd.sheetId], cmd.name);\n break;\n case \"HIDE_SHEET\":\n this.hideSheet(cmd.sheetId);\n break;\n case \"SHOW_SHEET\":\n this.showSheet(cmd.sheetId);\n break;\n case \"DUPLICATE_SHEET\":\n this.duplicateSheet(cmd.sheetId, cmd.sheetIdTo);\n break;\n case \"DELETE_SHEET\":\n this.deleteSheet(this.sheets[cmd.sheetId]);\n break;\n case \"REMOVE_COLUMNS_ROWS\":\n if (cmd.dimension === \"COL\") {\n this.removeColumns(this.sheets[cmd.sheetId], [...cmd.elements]);\n }\n else {\n this.removeRows(this.sheets[cmd.sheetId], [...cmd.elements]);\n }\n break;\n case \"ADD_COLUMNS_ROWS\":\n if (cmd.dimension === \"COL\") {\n this.addColumns(this.sheets[cmd.sheetId], cmd.base, cmd.position, cmd.quantity);\n }\n else {\n this.addRows(this.sheets[cmd.sheetId], cmd.base, cmd.position, cmd.quantity);\n }\n break;\n case \"UPDATE_CELL_POSITION\":\n this.updateCellPosition(cmd);\n break;\n case \"FREEZE_COLUMNS\":\n this.setPaneDivisions(cmd.sheetId, cmd.quantity, \"COL\");\n break;\n case \"FREEZE_ROWS\":\n this.setPaneDivisions(cmd.sheetId, cmd.quantity, \"ROW\");\n break;\n case \"UNFREEZE_ROWS\":\n this.setPaneDivisions(cmd.sheetId, 0, \"ROW\");\n break;\n case \"UNFREEZE_COLUMNS\":\n this.setPaneDivisions(cmd.sheetId, 0, \"COL\");\n break;\n case \"UNFREEZE_COLUMNS_ROWS\":\n this.setPaneDivisions(cmd.sheetId, 0, \"COL\");\n this.setPaneDivisions(cmd.sheetId, 0, \"ROW\");\n }\n }\n // ---------------------------------------------------------------------------\n // Import/Export\n // ---------------------------------------------------------------------------\n import(data) {\n var _a, _b;\n // we need to fill the sheetIds mapping first, because otherwise formulas\n // that depends on a sheet not already imported will not be able to be\n // compiled\n for (let sheet of data.sheets) {\n this.sheetIdsMapName[sheet.name] = sheet.id;\n }\n for (let sheetData of data.sheets) {\n const name = sheetData.name || _t(\"Sheet\") + (Object.keys(this.sheets).length + 1);\n const { colNumber, rowNumber } = this.getImportedSheetSize(sheetData);\n const sheet = {\n id: sheetData.id,\n name: name,\n numberOfCols: colNumber,\n rows: createDefaultRows(rowNumber),\n areGridLinesVisible: sheetData.areGridLinesVisible === undefined ? true : sheetData.areGridLinesVisible,\n isVisible: sheetData.isVisible,\n panes: {\n xSplit: ((_a = sheetData.panes) === null || _a === void 0 ? void 0 : _a.xSplit) || 0,\n ySplit: ((_b = sheetData.panes) === null || _b === void 0 ? void 0 : _b.ySplit) || 0,\n },\n };\n this.orderedSheetIds.push(sheet.id);\n this.sheets[sheet.id] = sheet;\n }\n }\n exportSheets(data) {\n data.sheets = this.orderedSheetIds.filter(isDefined$1).map((id) => {\n const sheet = this.sheets[id];\n const sheetData = {\n id: sheet.id,\n name: sheet.name,\n colNumber: sheet.numberOfCols,\n rowNumber: this.getters.getNumberRows(sheet.id),\n rows: {},\n cols: {},\n merges: [],\n cells: {},\n conditionalFormats: [],\n figures: [],\n filterTables: [],\n areGridLinesVisible: sheet.areGridLinesVisible === undefined ? true : sheet.areGridLinesVisible,\n isVisible: sheet.isVisible,\n };\n if (sheet.panes.xSplit || sheet.panes.ySplit) {\n sheetData.panes = sheet.panes;\n }\n return sheetData;\n });\n }\n export(data) {\n this.exportSheets(data);\n }\n exportForExcel(data) {\n this.exportSheets(data);\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n getGridLinesVisibility(sheetId) {\n return this.getSheet(sheetId).areGridLinesVisible;\n }\n tryGetSheet(sheetId) {\n return this.sheets[sheetId];\n }\n getSheet(sheetId) {\n const sheet = this.sheets[sheetId];\n if (!sheet) {\n throw new Error(`Sheet ${sheetId} not found.`);\n }\n return sheet;\n }\n isSheetVisible(sheetId) {\n return this.getSheet(sheetId).isVisible;\n }\n /**\n * Return the sheet name. Throw if the sheet is not found.\n */\n getSheetName(sheetId) {\n return this.getSheet(sheetId).name;\n }\n /**\n * Return the sheet name or undefined if the sheet doesn't exist.\n */\n tryGetSheetName(sheetId) {\n var _a;\n return (_a = this.tryGetSheet(sheetId)) === null || _a === void 0 ? void 0 : _a.name;\n }\n getSheetIdByName(name) {\n if (name) {\n const unquotedName = getUnquotedSheetName(name);\n for (const key in this.sheetIdsMapName) {\n if (key.toUpperCase() === unquotedName.toUpperCase()) {\n return this.sheetIdsMapName[key];\n }\n }\n }\n return undefined;\n }\n getSheetIds() {\n return this.orderedSheetIds;\n }\n getVisibleSheetIds() {\n return this.orderedSheetIds.filter(this.isSheetVisible.bind(this));\n }\n getEvaluationSheets() {\n return this.sheets;\n }\n doesHeaderExist(sheetId, dimension, index) {\n return dimension === \"COL\"\n ? index >= 0 && index < this.getNumberCols(sheetId)\n : index >= 0 && index < this.getNumberRows(sheetId);\n }\n getRow(sheetId, index) {\n const row = this.getSheet(sheetId).rows[index];\n if (!row) {\n throw new Error(`Row ${row} not found.`);\n }\n return row;\n }\n getCell({ sheetId, col, row }) {\n var _a;\n const sheet = this.tryGetSheet(sheetId);\n const cellId = (_a = sheet === null || sheet === void 0 ? void 0 : sheet.rows[row]) === null || _a === void 0 ? void 0 : _a.cells[col];\n if (cellId === undefined) {\n return undefined;\n }\n return this.getters.getCellById(cellId);\n }\n getColsZone(sheetId, start, end) {\n return {\n top: 0,\n bottom: this.getNumberRows(sheetId) - 1,\n left: start,\n right: end,\n };\n }\n getRowCells(sheetId, row) {\n var _a;\n return Object.values((_a = this.getSheet(sheetId).rows[row]) === null || _a === void 0 ? void 0 : _a.cells).filter(isDefined$1);\n }\n getRowsZone(sheetId, start, end) {\n return {\n top: start,\n bottom: end,\n left: 0,\n right: this.getSheet(sheetId).numberOfCols - 1,\n };\n }\n getCellPosition(cellId) {\n const cell = this.cellPosition[cellId];\n if (!cell) {\n throw new Error(`asking for a cell position that doesn't exist, cell id: ${cellId}`);\n }\n return cell;\n }\n getNumberCols(sheetId) {\n return this.getSheet(sheetId).numberOfCols;\n }\n getNumberRows(sheetId) {\n return this.getSheet(sheetId).rows.length;\n }\n getNumberHeaders(sheetId, dimension) {\n return dimension === \"COL\" ? this.getNumberCols(sheetId) : this.getNumberRows(sheetId);\n }\n getNextSheetName(baseName = \"Sheet\") {\n let i = 1;\n const names = this.orderedSheetIds.map(this.getSheetName.bind(this));\n let name = `${baseName}${i}`;\n while (names.includes(name)) {\n name = `${baseName}${i}`;\n i++;\n }\n return name;\n }\n getSheetSize(sheetId) {\n return {\n height: this.getNumberRows(sheetId),\n width: this.getNumberCols(sheetId),\n };\n }\n getSheetZone(sheetId) {\n return {\n top: 0,\n left: 0,\n bottom: this.getNumberRows(sheetId) - 1,\n right: this.getNumberCols(sheetId) - 1,\n };\n }\n getPaneDivisions(sheetId) {\n return this.getSheet(sheetId).panes;\n }\n setPaneDivisions(sheetId, base, dimension) {\n const panes = { ...this.getPaneDivisions(sheetId) };\n if (dimension === \"COL\") {\n panes.xSplit = base;\n }\n else if (dimension === \"ROW\") {\n panes.ySplit = base;\n }\n this.history.update(\"sheets\", sheetId, \"panes\", panes);\n }\n // ---------------------------------------------------------------------------\n // Row/Col manipulation\n // ---------------------------------------------------------------------------\n /**\n * Check if a zone only contains empty cells\n */\n isEmpty(sheetId, zone) {\n return positions(zone)\n .map(({ col, row }) => this.getCell({ sheetId, col, row }))\n .every((cell) => !cell || cell.content === \"\");\n }\n updateCellPosition(cmd) {\n const { sheetId, cellId, col, row } = cmd;\n if (cellId) {\n this.setNewPosition(cellId, sheetId, col, row);\n }\n else {\n this.clearPosition(sheetId, col, row);\n }\n }\n /**\n * Set the cell at a new position and clear its previous position.\n */\n setNewPosition(cellId, sheetId, col, row) {\n const currentPosition = this.cellPosition[cellId];\n if (currentPosition) {\n this.clearPosition(sheetId, currentPosition.col, currentPosition.row);\n }\n this.history.update(\"cellPosition\", cellId, {\n row: row,\n col: col,\n sheetId: sheetId,\n });\n this.history.update(\"sheets\", sheetId, \"rows\", row, \"cells\", col, cellId);\n }\n /**\n * Remove the cell at the given position (if there's one)\n */\n clearPosition(sheetId, col, row) {\n var _a;\n const cellId = (_a = this.sheets[sheetId]) === null || _a === void 0 ? void 0 : _a.rows[row].cells[col];\n if (cellId) {\n this.history.update(\"cellPosition\", cellId, undefined);\n this.history.update(\"sheets\", sheetId, \"rows\", row, \"cells\", col, undefined);\n }\n }\n setGridLinesVisibility(sheetId, areGridLinesVisible) {\n this.history.update(\"sheets\", sheetId, \"areGridLinesVisible\", areGridLinesVisible);\n }\n clearZones(sheetId, zones) {\n for (let zone of zones) {\n for (let col = zone.left; col <= zone.right; col++) {\n for (let row = zone.top; row <= zone.bottom; row++) {\n const cell = this.sheets[sheetId].rows[row].cells[col];\n if (cell) {\n this.dispatch(\"UPDATE_CELL\", {\n sheetId: sheetId,\n content: \"\",\n col,\n row,\n });\n }\n }\n }\n }\n }\n createSheet(id, name, colNumber, rowNumber, position) {\n const sheet = {\n id,\n name,\n numberOfCols: colNumber,\n rows: createDefaultRows(rowNumber),\n areGridLinesVisible: true,\n isVisible: true,\n panes: {\n xSplit: 0,\n ySplit: 0,\n },\n };\n const orderedSheetIds = this.orderedSheetIds.slice();\n orderedSheetIds.splice(position, 0, sheet.id);\n const sheets = this.sheets;\n this.history.update(\"orderedSheetIds\", orderedSheetIds);\n this.history.update(\"sheets\", Object.assign({}, sheets, { [sheet.id]: sheet }));\n return sheet;\n }\n moveSheet(sheetId, direction) {\n const orderedSheetIds = this.orderedSheetIds.slice();\n const currentIndex = orderedSheetIds.findIndex((id) => id === sheetId);\n const sheet = orderedSheetIds.splice(currentIndex, 1);\n let index = direction === \"left\"\n ? this.findIndexOfPreviousVisibleSheet(currentIndex - 1, orderedSheetIds)\n : this.findIndexOfNextVisibleSheet(currentIndex + 1, orderedSheetIds);\n if (index === undefined) {\n index = orderedSheetIds.length;\n }\n orderedSheetIds.splice(index, 0, sheet[0]);\n this.history.update(\"orderedSheetIds\", orderedSheetIds);\n }\n findIndexOfPreviousVisibleSheet(current, orderedSheetIds) {\n while (current >= 0 && !this.isSheetVisible(orderedSheetIds[current])) {\n current--;\n }\n if (current === -1) {\n throw new Error(\"There is no previous visible sheet\");\n }\n return current;\n }\n findIndexOfNextVisibleSheet(current, orderedSheetIds) {\n while (current < orderedSheetIds.length && !this.isSheetVisible(orderedSheetIds[current])) {\n current++;\n }\n if (current === orderedSheetIds.length - 1 &&\n !this.isSheetVisible(orderedSheetIds[current - 1])) {\n return undefined;\n }\n return current;\n }\n checkSheetName(cmd) {\n const { orderedSheetIds, sheets } = this;\n const name = cmd.name && cmd.name.trim().toLowerCase();\n if (orderedSheetIds.find((id) => { var _a; return ((_a = sheets[id]) === null || _a === void 0 ? void 0 : _a.name.toLowerCase()) === name; })) {\n return 11 /* CommandResult.DuplicatedSheetName */;\n }\n if (FORBIDDEN_IN_EXCEL_REGEX.test(name)) {\n return 13 /* CommandResult.ForbiddenCharactersInSheetName */;\n }\n return 0 /* CommandResult.Success */;\n }\n checkSheetPosition(cmd) {\n const { orderedSheetIds } = this;\n if (cmd.position > orderedSheetIds.length || cmd.position < 0) {\n return 15 /* CommandResult.WrongSheetPosition */;\n }\n return 0 /* CommandResult.Success */;\n }\n checkRowFreezeQuantity(cmd) {\n return cmd.quantity >= 1 && cmd.quantity < this.getNumberRows(cmd.sheetId)\n ? 0 /* CommandResult.Success */\n : 74 /* CommandResult.InvalidFreezeQuantity */;\n }\n checkColFreezeQuantity(cmd) {\n return cmd.quantity >= 1 && cmd.quantity < this.getNumberCols(cmd.sheetId)\n ? 0 /* CommandResult.Success */\n : 74 /* CommandResult.InvalidFreezeQuantity */;\n }\n checkRowFreezeOverlapMerge(cmd) {\n const merges = this.getters.getMerges(cmd.sheetId);\n for (let merge of merges) {\n if (merge.top < cmd.quantity && cmd.quantity <= merge.bottom) {\n return 65 /* CommandResult.MergeOverlap */;\n }\n }\n return 0 /* CommandResult.Success */;\n }\n checkColFreezeOverlapMerge(cmd) {\n const merges = this.getters.getMerges(cmd.sheetId);\n for (let merge of merges) {\n if (merge.left < cmd.quantity && cmd.quantity <= merge.right) {\n return 65 /* CommandResult.MergeOverlap */;\n }\n }\n return 0 /* CommandResult.Success */;\n }\n isRenameAllowed(cmd) {\n const name = cmd.name && cmd.name.trim().toLowerCase();\n if (!name) {\n return 10 /* CommandResult.MissingSheetName */;\n }\n return this.checkSheetName(cmd);\n }\n renameSheet(sheet, name) {\n const oldName = sheet.name;\n this.history.update(\"sheets\", sheet.id, \"name\", name.trim());\n const sheetIdsMapName = Object.assign({}, this.sheetIdsMapName);\n sheetIdsMapName[name] = sheet.id;\n delete sheetIdsMapName[oldName];\n this.history.update(\"sheetIdsMapName\", sheetIdsMapName);\n }\n hideSheet(sheetId) {\n this.history.update(\"sheets\", sheetId, \"isVisible\", false);\n }\n showSheet(sheetId) {\n this.history.update(\"sheets\", sheetId, \"isVisible\", true);\n }\n duplicateSheet(fromId, toId) {\n const sheet = this.getSheet(fromId);\n const toName = this.getDuplicateSheetName(sheet.name);\n const newSheet = JSON.parse(JSON.stringify(sheet));\n newSheet.id = toId;\n newSheet.name = toName;\n for (let col = 0; col <= newSheet.numberOfCols; col++) {\n for (let row = 0; row <= newSheet.rows.length; row++) {\n if (newSheet.rows[row]) {\n newSheet.rows[row].cells[col] = undefined;\n }\n }\n }\n const orderedSheetIds = this.orderedSheetIds.slice();\n const currentIndex = orderedSheetIds.indexOf(fromId);\n orderedSheetIds.splice(currentIndex + 1, 0, newSheet.id);\n this.history.update(\"orderedSheetIds\", orderedSheetIds);\n this.history.update(\"sheets\", Object.assign({}, this.sheets, { [newSheet.id]: newSheet }));\n for (const cell of Object.values(this.getters.getCells(fromId))) {\n const { col, row } = this.getCellPosition(cell.id);\n this.dispatch(\"UPDATE_CELL\", {\n sheetId: newSheet.id,\n col,\n row,\n content: cell.content,\n format: cell.format,\n style: cell.style,\n });\n }\n const sheetIdsMapName = Object.assign({}, this.sheetIdsMapName);\n sheetIdsMapName[newSheet.name] = newSheet.id;\n this.history.update(\"sheetIdsMapName\", sheetIdsMapName);\n }\n getDuplicateSheetName(sheetName) {\n let i = 1;\n const names = this.orderedSheetIds.map(this.getSheetName.bind(this));\n const baseName = _lt(\"Copy of %s\", sheetName);\n let name = baseName.toString();\n while (names.includes(name)) {\n name = `${baseName} (${i})`;\n i++;\n }\n return name;\n }\n deleteSheet(sheet) {\n const name = sheet.name;\n const sheets = Object.assign({}, this.sheets);\n delete sheets[sheet.id];\n this.history.update(\"sheets\", sheets);\n const orderedSheetIds = this.orderedSheetIds.slice();\n const currentIndex = orderedSheetIds.indexOf(sheet.id);\n orderedSheetIds.splice(currentIndex, 1);\n this.history.update(\"orderedSheetIds\", orderedSheetIds);\n const sheetIdsMapName = Object.assign({}, this.sheetIdsMapName);\n delete sheetIdsMapName[name];\n this.history.update(\"sheetIdsMapName\", sheetIdsMapName);\n }\n /**\n * Delete column. This requires a lot of handling:\n * - Update all the formulas in all sheets\n * - Move the cells\n * - Update the cols/rows (size, number, (cells), ...)\n * - Reevaluate the cells\n *\n * @param sheet ID of the sheet on which deletion should be applied\n * @param columns Columns to delete\n */\n removeColumns(sheet, columns) {\n // This is necessary because we have to delete elements in correct order:\n // begin with the end.\n columns.sort((a, b) => b - a);\n for (let column of columns) {\n // Move the cells.\n this.moveCellOnColumnsDeletion(sheet, column);\n }\n const numberOfCols = this.sheets[sheet.id].numberOfCols;\n this.history.update(\"sheets\", sheet.id, \"numberOfCols\", numberOfCols - columns.length);\n const count = columns.filter((col) => col < sheet.panes.xSplit).length;\n if (count) {\n this.setPaneDivisions(sheet.id, sheet.panes.xSplit - count, \"COL\");\n }\n }\n /**\n * Delete row. This requires a lot of handling:\n * - Update the merges\n * - Update all the formulas in all sheets\n * - Move the cells\n * - Update the cols/rows (size, number, (cells), ...)\n * - Reevaluate the cells\n *\n * @param sheet ID of the sheet on which deletion should be applied\n * @param rows Rows to delete\n */\n removeRows(sheet, rows) {\n // This is necessary because we have to delete elements in correct order:\n // begin with the end.\n rows.sort((a, b) => b - a);\n for (let group of groupConsecutive(rows)) {\n // Move the cells.\n this.moveCellOnRowsDeletion(sheet, group[group.length - 1], group[0]);\n // Effectively delete the element and recompute the left-right/top-bottom.\n group.map((row) => this.updateRowsStructureOnDeletion(row, sheet));\n }\n const count = rows.filter((row) => row < sheet.panes.ySplit).length;\n if (count) {\n this.setPaneDivisions(sheet.id, sheet.panes.ySplit - count, \"ROW\");\n }\n }\n addColumns(sheet, column, position, quantity) {\n const index = position === \"before\" ? column : column + 1;\n // Move the cells.\n this.moveCellsOnAddition(sheet, index, quantity, \"columns\");\n const numberOfCols = this.sheets[sheet.id].numberOfCols;\n this.history.update(\"sheets\", sheet.id, \"numberOfCols\", numberOfCols + quantity);\n if (index < sheet.panes.xSplit) {\n this.setPaneDivisions(sheet.id, sheet.panes.xSplit + quantity, \"COL\");\n }\n }\n addRows(sheet, row, position, quantity) {\n const index = position === \"before\" ? row : row + 1;\n this.addEmptyRows(sheet, quantity);\n // Move the cells.\n this.moveCellsOnAddition(sheet, index, quantity, \"rows\");\n // Recompute the left-right/top-bottom.\n this.updateRowsStructureOnAddition(sheet, row, quantity);\n if (index < sheet.panes.ySplit) {\n this.setPaneDivisions(sheet.id, sheet.panes.ySplit + quantity, \"ROW\");\n }\n }\n moveCellOnColumnsDeletion(sheet, deletedColumn) {\n for (let [index, row] of Object.entries(sheet.rows)) {\n const rowIndex = parseInt(index, 10);\n for (let i in row.cells) {\n const colIndex = parseInt(i, 10);\n const cellId = row.cells[i];\n if (cellId) {\n if (colIndex === deletedColumn) {\n this.dispatch(\"CLEAR_CELL\", {\n sheetId: sheet.id,\n col: colIndex,\n row: rowIndex,\n });\n }\n if (colIndex > deletedColumn) {\n this.dispatch(\"UPDATE_CELL_POSITION\", {\n sheetId: sheet.id,\n cellId: cellId,\n col: colIndex - 1,\n row: rowIndex,\n });\n }\n }\n }\n }\n }\n /**\n * Move the cells after a column or rows insertion\n */\n moveCellsOnAddition(sheet, addedElement, quantity, dimension) {\n const commands = [];\n for (const [index, row] of Object.entries(sheet.rows)) {\n const rowIndex = parseInt(index, 10);\n if (dimension !== \"rows\" || rowIndex >= addedElement) {\n for (let i in row.cells) {\n const colIndex = parseInt(i, 10);\n const cellId = row.cells[i];\n if (cellId) {\n if (dimension === \"rows\" || colIndex >= addedElement) {\n commands.push({\n type: \"UPDATE_CELL_POSITION\",\n sheetId: sheet.id,\n cellId: cellId,\n col: colIndex + (dimension === \"columns\" ? quantity : 0),\n row: rowIndex + (dimension === \"rows\" ? quantity : 0),\n });\n }\n }\n }\n }\n }\n for (let cmd of commands.reverse()) {\n this.dispatch(cmd.type, cmd);\n }\n }\n /**\n * Move all the cells that are from the row under `deleteToRow` up to `deleteFromRow`\n *\n * b.e.\n * move vertically with delete from 3 and delete to 5 will first clear all the cells from lines 3 to 5,\n * then take all the row starting at index 6 and add them back at index 3\n *\n */\n moveCellOnRowsDeletion(sheet, deleteFromRow, deleteToRow) {\n const numberRows = deleteToRow - deleteFromRow + 1;\n for (let [index, row] of Object.entries(sheet.rows)) {\n const rowIndex = parseInt(index, 10);\n if (rowIndex >= deleteFromRow && rowIndex <= deleteToRow) {\n for (let i in row.cells) {\n const colIndex = parseInt(i, 10);\n const cellId = row.cells[i];\n if (cellId) {\n this.dispatch(\"CLEAR_CELL\", {\n sheetId: sheet.id,\n col: colIndex,\n row: rowIndex,\n });\n }\n }\n }\n if (rowIndex > deleteToRow) {\n for (let i in row.cells) {\n const colIndex = parseInt(i, 10);\n const cellId = row.cells[i];\n if (cellId) {\n this.dispatch(\"UPDATE_CELL_POSITION\", {\n sheetId: sheet.id,\n cellId: cellId,\n col: colIndex,\n row: rowIndex - numberRows,\n });\n }\n }\n }\n }\n }\n updateRowsStructureOnDeletion(index, sheet) {\n const rows = [];\n const cellsQueue = sheet.rows.map((row) => row.cells);\n for (let i in sheet.rows) {\n if (parseInt(i, 10) === index) {\n continue;\n }\n rows.push({\n cells: cellsQueue.shift(),\n });\n }\n this.history.update(\"sheets\", sheet.id, \"rows\", rows);\n }\n /**\n * Update the rows of the sheet after an addition:\n * - Rename the rows\n *\n * @param sheet Sheet on which the deletion occurs\n * @param addedRow Index of the added row\n * @param rowsToAdd Number of the rows to add\n */\n updateRowsStructureOnAddition(sheet, addedRow, rowsToAdd) {\n const rows = [];\n const cellsQueue = sheet.rows.map((row) => row.cells);\n sheet.rows.forEach(() => rows.push({\n cells: cellsQueue.shift(),\n }));\n this.history.update(\"sheets\", sheet.id, \"rows\", rows);\n }\n /**\n * Add empty rows at the end of the rows\n *\n * @param sheet Sheet\n * @param quantity Number of rows to add\n */\n addEmptyRows(sheet, quantity) {\n const rows = sheet.rows.slice();\n for (let i = 0; i < quantity; i++) {\n rows.push({\n cells: {},\n });\n }\n this.history.update(\"sheets\", sheet.id, \"rows\", rows);\n }\n getImportedSheetSize(data) {\n const positions = Object.keys(data.cells).map(toCartesian);\n let rowNumber = data.rowNumber;\n let colNumber = data.colNumber;\n for (let { col, row } of positions) {\n rowNumber = Math.max(rowNumber, row + 1);\n colNumber = Math.max(colNumber, col + 1);\n }\n return { rowNumber, colNumber };\n }\n // ----------------------------------------------------\n // HIDE / SHOW\n // ----------------------------------------------------\n /**\n * Check that any \"sheetId\" in the command matches an existing\n * sheet.\n */\n checkSheetExists(cmd) {\n if (cmd.type !== \"CREATE_SHEET\" && \"sheetId\" in cmd && this.sheets[cmd.sheetId] === undefined) {\n return 27 /* CommandResult.InvalidSheetId */;\n }\n else if (cmd.type === \"CREATE_SHEET\" && this.sheets[cmd.sheetId] !== undefined) {\n return 12 /* CommandResult.DuplicatedSheetId */;\n }\n return 0 /* CommandResult.Success */;\n }\n /**\n * Check if zones in the command are well formed and\n * not outside the sheet.\n */\n checkZones(cmd) {\n const zones = [];\n if (\"zone\" in cmd) {\n zones.push(cmd.zone);\n }\n if (\"target\" in cmd && Array.isArray(cmd.target)) {\n zones.push(...cmd.target);\n }\n if (\"ranges\" in cmd && Array.isArray(cmd.ranges)) {\n zones.push(...cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData).zone));\n }\n if (!zones.every(isZoneValid)) {\n return 25 /* CommandResult.InvalidRange */;\n }\n else if (zones.length && \"sheetId\" in cmd) {\n const sheetZone = this.getSheetZone(cmd.sheetId);\n return zones.every((zone) => isZoneInside(zone, sheetZone))\n ? 0 /* CommandResult.Success */\n : 18 /* CommandResult.TargetOutOfSheet */;\n }\n return 0 /* CommandResult.Success */;\n }\n }\n SheetPlugin.getters = [\n \"getSheetName\",\n \"tryGetSheetName\",\n \"getSheet\",\n \"tryGetSheet\",\n \"getSheetIdByName\",\n \"getSheetIds\",\n \"getVisibleSheetIds\",\n \"isSheetVisible\",\n \"getEvaluationSheets\",\n \"doesHeaderExist\",\n \"getCell\",\n \"getCellPosition\",\n \"getColsZone\",\n \"getRowCells\",\n \"getRowsZone\",\n \"getNumberCols\",\n \"getNumberRows\",\n \"getNumberHeaders\",\n \"getGridLinesVisibility\",\n \"getNextSheetName\",\n \"isEmpty\",\n \"getSheetSize\",\n \"getSheetZone\",\n \"getPaneDivisions\",\n ];\n\n /**\n * https://tomekdev.com/posts/sorting-colors-in-js\n */\n function sortWithClusters(colorsToSort) {\n const clusters = [\n { leadColor: rgba(255, 0, 0), colors: [] },\n { leadColor: rgba(255, 128, 0), colors: [] },\n { leadColor: rgba(128, 128, 0), colors: [] },\n { leadColor: rgba(128, 255, 0), colors: [] },\n { leadColor: rgba(0, 255, 0), colors: [] },\n { leadColor: rgba(0, 255, 128), colors: [] },\n { leadColor: rgba(0, 255, 255), colors: [] },\n { leadColor: rgba(0, 127, 255), colors: [] },\n { leadColor: rgba(0, 0, 255), colors: [] },\n { leadColor: rgba(127, 0, 255), colors: [] },\n { leadColor: rgba(128, 0, 128), colors: [] },\n { leadColor: rgba(255, 0, 128), colors: [] }, // rose\n ];\n for (const color of colorsToSort.map(colorToRGBA)) {\n let currentDistance = 500; //max distance is 441;\n let currentIndex = 0;\n clusters.forEach((cluster, clusterIndex) => {\n const distance = colorDistance(color, cluster.leadColor);\n if (currentDistance > distance) {\n currentDistance = distance;\n currentIndex = clusterIndex;\n }\n });\n clusters[currentIndex].colors.push(color);\n }\n return clusters\n .map((cluster) => cluster.colors.sort((a, b) => rgbaToHSLA(a).s - rgbaToHSLA(b).s))\n .flat()\n .map(rgbaToHex);\n }\n function colorDistance(color1, color2) {\n return Math.sqrt(Math.pow(color1.r - color2.r, 2) +\n Math.pow(color1.g - color2.g, 2) +\n Math.pow(color1.b - color2.b, 2));\n }\n /**\n * CustomColors plugin\n * This plugins aims to compute and keep to custom colors used in the\n * current spreadsheet\n */\n class CustomColorsPlugin extends UIPlugin {\n constructor() {\n super(...arguments);\n this.customColors = new Set();\n this.shouldUpdateColors = false;\n }\n handle(cmd) {\n switch (cmd.type) {\n case \"UPDATE_CELL\":\n case \"UPDATE_CHART\":\n case \"CREATE_CHART\":\n case \"ADD_CONDITIONAL_FORMAT\":\n this.shouldUpdateColors = true;\n }\n }\n finalize() {\n if (this.shouldUpdateColors) {\n this.shouldUpdateColors = false;\n for (const color of this.getCustomColors()) {\n this.tryToAddColor(color);\n }\n }\n }\n getCustomColors() {\n let usedColors = [];\n for (const sheetId of this.getters.getSheetIds()) {\n const cells = Object.values(this.getters.getCells(sheetId));\n usedColors = usedColors.concat(this.getColorsFromCells(cells), this.getFormattingColors(sheetId), this.getChartColors(sheetId));\n }\n return sortWithClusters([\n ...new Set(\n // remove duplicates first to check validity on a reduced\n // set of colors, then normalize to HEX and remove duplicates\n // again\n [...new Set([...usedColors, ...this.customColors])]\n .filter(isColorValid)\n .map((c) => toHex(c).toLowerCase())),\n ]).filter((color) => !COLOR_PICKER_DEFAULTS.includes(color));\n }\n getColorsFromCells(cells) {\n var _a, _b;\n const colors = new Set();\n for (const cell of cells) {\n if ((_a = cell.style) === null || _a === void 0 ? void 0 : _a.textColor) {\n colors.add(cell.style.textColor);\n }\n if ((_b = cell.style) === null || _b === void 0 ? void 0 : _b.fillColor) {\n colors.add(cell.style.fillColor);\n }\n }\n return [...colors];\n }\n getFormattingColors(sheetId) {\n const formats = this.getters.getConditionalFormats(sheetId);\n const formatColors = [];\n for (const format of formats) {\n const rule = format.rule;\n if (rule.type === \"CellIsRule\") {\n formatColors.push(rule.style.textColor);\n formatColors.push(rule.style.fillColor);\n }\n else if (rule.type === \"ColorScaleRule\") {\n formatColors.push(colorNumberString(rule.minimum.color));\n formatColors.push(rule.midpoint ? colorNumberString(rule.midpoint.color) : undefined);\n formatColors.push(colorNumberString(rule.maximum.color));\n }\n }\n return formatColors.filter(isDefined$1);\n }\n getChartColors(sheetId) {\n const charts = this.getters.getChartIds(sheetId).map((cid) => this.getters.getChart(cid));\n let chartsColors = new Set();\n for (let chart of charts) {\n if (chart === undefined) {\n continue;\n }\n const background = chart.getDefinition().background;\n if (background !== undefined) {\n chartsColors.add(background);\n }\n switch (chart.type) {\n case \"gauge\":\n const colors = chart.sectionRule.colors;\n chartsColors.add(colors.lowerColor);\n chartsColors.add(colors.middleColor);\n chartsColors.add(colors.upperColor);\n break;\n case \"scorecard\":\n const scoreChart = chart;\n chartsColors.add(scoreChart.baselineColorDown);\n chartsColors.add(scoreChart.baselineColorUp);\n break;\n }\n }\n return [...chartsColors];\n }\n tryToAddColor(color) {\n const formattedColor = toHex(color).toLowerCase();\n if (color && !COLOR_PICKER_DEFAULTS.includes(formattedColor)) {\n this.customColors.add(formattedColor);\n }\n }\n }\n CustomColorsPlugin.getters = [\"getCustomColors\"];\n\n const functionMap = functionRegistry.mapping;\n class EvaluationPlugin extends UIPlugin {\n constructor(config) {\n super(config);\n this.isUpToDate = false;\n this.evaluatedCells = {};\n this.evalContext = config.custom;\n this.lazyEvaluation = config.lazyEvaluation;\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n handle(cmd) {\n if (invalidateEvaluationCommands.has(cmd.type)) {\n this.isUpToDate = false;\n }\n switch (cmd.type) {\n case \"UPDATE_CELL\":\n if (\"content\" in cmd || \"format\" in cmd) {\n this.isUpToDate = false;\n }\n break;\n case \"EVALUATE_CELLS\":\n this.evaluate();\n break;\n }\n }\n finalize() {\n if (!this.isUpToDate) {\n this.evaluate();\n this.isUpToDate = true;\n }\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n evaluateFormula(formulaString, sheetId = this.getters.getActiveSheetId()) {\n const compiledFormula = compile(formulaString);\n const params = this.getCompilationParameters((cell) => this.getEvaluatedCell(this.getters.getCellPosition(cell.id)));\n const ranges = [];\n for (let xc of compiledFormula.dependencies) {\n ranges.push(this.getters.getRangeFromSheetXC(sheetId, xc));\n }\n return compiledFormula.execute(ranges, ...params).value;\n }\n /**\n * Return the value of each cell in the range as they are displayed in the grid.\n */\n getRangeFormattedValues(range) {\n const sheet = this.getters.tryGetSheet(range.sheetId);\n if (sheet === undefined)\n return [];\n return this.getters\n .getEvaluatedCellsInZone(sheet.id, range.zone)\n .map((cell) => cell.formattedValue);\n }\n /**\n * Return the value of each cell in the range.\n */\n getRangeValues(range) {\n const sheet = this.getters.tryGetSheet(range.sheetId);\n if (sheet === undefined)\n return [];\n return this.getters.getEvaluatedCellsInZone(sheet.id, range.zone).map((cell) => cell.value);\n }\n getEvaluatedCell({ sheetId, col, row }) {\n var _a, _b, _c;\n const cell = this.getters.getCell({ sheetId, col, row });\n if (cell === undefined) {\n return createEvaluatedCell(\"\");\n }\n // the cell might have been created by a command in the current\n // dispatch but the evaluation is not done yet.\n return ((_c = (_b = (_a = this.evaluatedCells[sheetId]) === null || _a === void 0 ? void 0 : _a[col]) === null || _b === void 0 ? void 0 : _b[row]) === null || _c === void 0 ? void 0 : _c.call(_b)) || createEvaluatedCell(\"\");\n }\n getEvaluatedCells(sheetId) {\n const rawCells = this.getters.getCells(sheetId) || {};\n const record = {};\n for (let cellId of Object.keys(rawCells)) {\n const position = this.getters.getCellPosition(cellId);\n record[cellId] = this.getEvaluatedCell(position);\n }\n return record;\n }\n /**\n * Returns all the evaluated cells of a col\n */\n getColEvaluatedCells(sheetId, col) {\n var _a;\n return Object.values(((_a = this.evaluatedCells[sheetId]) === null || _a === void 0 ? void 0 : _a[col]) || [])\n .filter(isDefined$1)\n .map((lazyCell) => lazyCell());\n }\n getEvaluatedCellsInZone(sheetId, zone) {\n return positions(zone).map(({ col, row }) => this.getters.getEvaluatedCell({ sheetId, col, row }));\n }\n // ---------------------------------------------------------------------------\n // Evaluator\n // ---------------------------------------------------------------------------\n setEvaluatedCell(cellId, evaluatedCell) {\n const { col, row, sheetId } = this.getters.getCellPosition(cellId);\n if (!this.evaluatedCells[sheetId]) {\n this.evaluatedCells[sheetId] = {};\n }\n if (!this.evaluatedCells[sheetId][col]) {\n this.evaluatedCells[sheetId][col] = {};\n }\n this.evaluatedCells[sheetId][col][row] = evaluatedCell;\n if (!this.lazyEvaluation) {\n this.evaluatedCells[sheetId][col][row]();\n }\n }\n *getAllCells() {\n // use a generator function to avoid re-building a new object\n for (const sheetId of this.getters.getSheetIds()) {\n const cells = this.getters.getCells(sheetId);\n for (const cellId in cells) {\n yield cells[cellId];\n }\n }\n }\n evaluate() {\n this.evaluatedCells = {};\n const cellsBeingComputed = new Set();\n const computeCell = (cell) => {\n var _a, _b;\n const cellId = cell.id;\n const { col, row, sheetId } = this.getters.getCellPosition(cellId);\n const lazyEvaluation = (_b = (_a = this.evaluatedCells[sheetId]) === null || _a === void 0 ? void 0 : _a[col]) === null || _b === void 0 ? void 0 : _b[row];\n if (lazyEvaluation) {\n return lazyEvaluation; // already computed\n }\n return lazy(() => {\n try {\n switch (cell.isFormula) {\n case true:\n return computeFormulaCell(cell);\n case false:\n return evaluateLiteral(cell.content, cell.format);\n }\n }\n catch (e) {\n return handleError(e, cell);\n }\n });\n };\n const handleError = (e, cell) => {\n if (!(e instanceof Error)) {\n e = new Error(e);\n }\n const msg = (e === null || e === void 0 ? void 0 : e.errorType) || CellErrorType.GenericError;\n // apply function name\n const __lastFnCalled = compilationParameters[2].__lastFnCalled || \"\";\n const error = new EvaluationError(msg, e.message.replace(\"[[FUNCTION_NAME]]\", __lastFnCalled), e.logLevel !== undefined ? e.logLevel : CellErrorLevel.error);\n return errorCell(cell.content, error);\n };\n const computeFormulaCell = (cellData) => {\n const cellId = cellData.id;\n if (cellsBeingComputed.has(cellId)) {\n throw new CircularDependencyError();\n }\n compilationParameters[2].__originCellXC = () => {\n // compute the value lazily for performance reasons\n const position = compilationParameters[2].getters.getCellPosition(cellId);\n return toXC(position.col, position.row);\n };\n cellsBeingComputed.add(cellId);\n const computedCell = cellData.compiledFormula.execute(cellData.dependencies, ...compilationParameters);\n cellsBeingComputed.delete(cellId);\n if (Array.isArray(computedCell.value)) {\n // if a value returns an array (like =A1:A3)\n throw new Error(_lt(\"This formula depends on invalid values\"));\n }\n return createEvaluatedCell(computedCell.value, cellData.format || computedCell.format);\n };\n const compilationParameters = this.getCompilationParameters((cell) => computeCell(cell)());\n for (const cell of this.getAllCells()) {\n this.setEvaluatedCell(cell.id, computeCell(cell));\n }\n }\n /**\n * Return all functions necessary to properly evaluate a formula:\n * - a refFn function to read any reference, cell or range of a normalized formula\n * - a range function to convert any reference to a proper value array\n * - an evaluation context\n */\n getCompilationParameters(computeCell) {\n const evalContext = Object.assign(Object.create(functionMap), this.evalContext, {\n getters: this.getters,\n });\n const getters = this.getters;\n function readCell(range) {\n let cell;\n if (!getters.tryGetSheet(range.sheetId)) {\n throw new Error(_lt(\"Invalid sheet name\"));\n }\n cell = getters.getCell({ sheetId: range.sheetId, col: range.zone.left, row: range.zone.top });\n if (!cell || cell.content === \"\") {\n // magic \"empty\" value\n // Returning {value: null} instead of undefined will ensure that we don't\n // fall back on the default value of the argument provided to the formula's compute function\n return { value: null };\n }\n return getEvaluatedCell(cell);\n }\n const getEvaluatedCell = (cell) => {\n const evaluatedCell = computeCell(cell);\n if (evaluatedCell.type === CellValueType.error) {\n throw evaluatedCell.error;\n }\n return evaluatedCell;\n };\n /**\n * Return the values of the cell(s) used in reference, but always in the format of a range even\n * if a single cell is referenced. It is a list of col values. This is useful for the formulas that describe parameters as\n * range etc.\n *\n * Note that each col is possibly sparse: it only contain the values of cells\n * that are actually present in the grid.\n */\n function range(range) {\n const sheetId = range.sheetId;\n if (!isZoneValid(range.zone)) {\n throw new InvalidReferenceError();\n }\n // Performance issue: Avoid fetching data on positions that are out of the spreadsheet\n // e.g. A1:ZZZ9999 in a sheet with 10 cols and 10 rows should ignore everything past J10 and return a 10x10 array\n const sheetZone = getters.getSheetZone(sheetId);\n const result = [];\n const zone = intersection(range.zone, sheetZone);\n if (!zone) {\n result.push([]);\n return result;\n }\n // Performance issue: nested loop is faster than a map here\n for (let col = zone.left; col <= zone.right; col++) {\n const rowValues = [];\n for (let row = zone.top; row <= zone.bottom; row++) {\n const cell = evalContext.getters.getCell({ sheetId: range.sheetId, col, row });\n rowValues.push(cell ? getEvaluatedCell(cell) : undefined);\n }\n result.push(rowValues);\n }\n return result;\n }\n /**\n * Returns the value of the cell(s) used in reference\n *\n * @param range the references used\n * @param isMeta if a reference is supposed to be used in a `meta` parameter as described in the\n * function for which this parameter is used, we just return the string of the parameter.\n * The `compute` of the formula's function must process it completely\n */\n function refFn(range, isMeta, functionName, paramNumber) {\n if (isMeta) {\n // Use zoneToXc of zone instead of getRangeString to avoid sending unbounded ranges\n return { value: zoneToXc(range.zone) };\n }\n if (!isZoneValid(range.zone)) {\n throw new InvalidReferenceError();\n }\n // if the formula definition could have accepted a range, we would pass through the _range function and not here\n if (range.zone.bottom !== range.zone.top || range.zone.left !== range.zone.right) {\n throw new Error(paramNumber\n ? _lt(\"Function %s expects the parameter %s to be a single value or a single cell reference, not a range.\", functionName.toString(), paramNumber.toString())\n : _lt(\"Function %s expects its parameters to be single values or single cell references, not ranges.\", functionName.toString()));\n }\n if (range.invalidSheetName) {\n throw new Error(_lt(\"Invalid sheet name: %s\", range.invalidSheetName));\n }\n return readCell(range);\n }\n return [refFn, range, evalContext];\n }\n // ---------------------------------------------------------------------------\n // Export\n // ---------------------------------------------------------------------------\n exportForExcel(data) {\n for (let sheet of data.sheets) {\n for (const xc in sheet.cells) {\n const position = { sheetId: sheet.id, ...toCartesian(xc) };\n const cell = this.getters.getCell(position);\n if (cell) {\n const exportedCellData = sheet.cells[xc];\n exportedCellData.value = this.getEvaluatedCell(position).value;\n exportedCellData.isFormula = cell.isFormula && !this.isBadExpression(cell.content);\n }\n }\n }\n }\n isBadExpression(formula) {\n try {\n compile(formula);\n return false;\n }\n catch (error) {\n return true;\n }\n }\n }\n EvaluationPlugin.getters = [\n \"evaluateFormula\",\n \"getRangeFormattedValues\",\n \"getRangeValues\",\n \"getEvaluatedCell\",\n \"getEvaluatedCells\",\n \"getColEvaluatedCells\",\n \"getEvaluatedCellsInZone\",\n ];\n\n class EvaluationChartPlugin extends UIPlugin {\n constructor() {\n super(...arguments);\n this.charts = {};\n this.createRuntimeChart = chartRuntimeFactory(this.getters);\n }\n handle(cmd) {\n if (invalidateEvaluationCommands.has(cmd.type) ||\n invalidateCFEvaluationCommands.has(cmd.type) ||\n cmd.type === \"EVALUATE_CELLS\" ||\n cmd.type === \"UPDATE_CELL\") {\n for (const chartId in this.charts) {\n this.charts[chartId] = undefined;\n }\n }\n switch (cmd.type) {\n case \"UPDATE_CHART\":\n case \"CREATE_CHART\":\n case \"DELETE_FIGURE\":\n this.charts[cmd.id] = undefined;\n break;\n case \"DELETE_SHEET\":\n for (let chartId in this.charts) {\n if (!this.getters.isChartDefined(chartId)) {\n this.charts[chartId] = undefined;\n }\n }\n break;\n }\n }\n getChartRuntime(figureId) {\n if (!this.charts[figureId]) {\n const chart = this.getters.getChart(figureId);\n if (!chart) {\n throw new Error(`No chart for the given id: ${figureId}`);\n }\n this.charts[figureId] = this.createRuntimeChart(chart);\n }\n return this.charts[figureId];\n }\n /**\n * Get the background color of a chart based on the color of the first cell of the main range\n * of the chart. In order of priority, it will return :\n *\n * - the chart background color if one is defined\n * - the fill color of the cell if one is defined\n * - the fill color of the cell from conditional formats if one is defined\n * - the default chart color if no other color is defined\n */\n getBackgroundOfSingleCellChart(chartBackground, mainRange) {\n if (chartBackground)\n return chartBackground;\n if (!mainRange) {\n return BACKGROUND_CHART_COLOR;\n }\n const col = mainRange.zone.left;\n const row = mainRange.zone.top;\n const sheetId = mainRange.sheetId;\n const style = this.getters.getCellComputedStyle({ sheetId, col, row });\n return style.fillColor || BACKGROUND_CHART_COLOR;\n }\n }\n EvaluationChartPlugin.getters = [\"getChartRuntime\", \"getBackgroundOfSingleCellChart\"];\n\n // -----------------------------------------------------------------------------\n // Constants\n // -----------------------------------------------------------------------------\n class EvaluationConditionalFormatPlugin extends UIPlugin {\n constructor() {\n super(...arguments);\n this.isStale = true;\n // stores the computed styles in the format of computedStyles.sheetName[col][row] = Style\n this.computedStyles = {};\n this.computedIcons = {};\n /**\n * Execute the predicate to know if a conditional formatting rule should be applied to a cell\n */\n this.rulePredicate = {\n CellIsRule: (cell, rule) => {\n if (cell.type === CellValueType.error) {\n return false;\n }\n const values = rule.values.map(parseLiteral);\n switch (rule.operator) {\n case \"IsEmpty\":\n return cell.value.toString().trim() === \"\";\n case \"IsNotEmpty\":\n return cell.value.toString().trim() !== \"\";\n case \"BeginsWith\":\n if (values[0] === \"\") {\n return false;\n }\n return cell.value.toString().startsWith(values[0].toString());\n case \"EndsWith\":\n if (values[0] === \"\") {\n return false;\n }\n return cell.value.toString().endsWith(values[0].toString());\n case \"Between\":\n return cell.value >= values[0] && cell.value <= values[1];\n case \"NotBetween\":\n return !(cell.value >= values[0] && cell.value <= values[1]);\n case \"ContainsText\":\n return cell.value.toString().indexOf(values[0].toString()) > -1;\n case \"NotContains\":\n return !cell.value || cell.value.toString().indexOf(values[0].toString()) == -1;\n case \"GreaterThan\":\n return cell.value > values[0];\n case \"GreaterThanOrEqual\":\n return cell.value >= values[0];\n case \"LessThan\":\n return cell.value < values[0];\n case \"LessThanOrEqual\":\n return cell.value <= values[0];\n case \"NotEqual\":\n if (values[0] === \"\") {\n return false;\n }\n return cell.value !== values[0];\n case \"Equal\":\n if (values[0] === \"\") {\n return true;\n }\n return cell.value === values[0];\n default:\n console.warn(_lt(\"Not implemented operator %s for kind of conditional formatting: %s\", rule.operator, rule.type));\n }\n return false;\n },\n };\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n handle(cmd) {\n if (invalidateCFEvaluationCommands.has(cmd.type) ||\n (cmd.type === \"UPDATE_CELL\" && \"content\" in cmd)) {\n this.isStale = true;\n }\n switch (cmd.type) {\n case \"ACTIVATE_SHEET\":\n const activeSheet = cmd.sheetIdTo;\n this.computedStyles[activeSheet] = this.computedStyles[activeSheet] || {};\n this.computedIcons[activeSheet] = this.computedIcons[activeSheet] || {};\n this.isStale = true;\n break;\n case \"AUTOFILL_CELL\":\n const sheetId = this.getters.getActiveSheetId();\n const cfOrigin = this.getters.getRulesByCell(sheetId, cmd.originCol, cmd.originRow);\n for (const cf of cfOrigin) {\n this.adaptRules(sheetId, cf, [toXC(cmd.col, cmd.row)], []);\n }\n break;\n case \"PASTE_CONDITIONAL_FORMAT\":\n this.pasteCf(cmd.origin, cmd.target, cmd.operation);\n break;\n }\n }\n finalize() {\n if (this.isStale) {\n this.computeStyles();\n this.isStale = false;\n }\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n getCellComputedStyle(position) {\n var _a;\n // TODO move this getter out of CF: it also depends on filters and link\n const { sheetId, col, row } = position;\n const cell = this.getters.getCell(position);\n const styles = this.computedStyles[sheetId];\n const cfStyle = styles && ((_a = styles[col]) === null || _a === void 0 ? void 0 : _a[row]);\n const computedStyle = {\n ...cell === null || cell === void 0 ? void 0 : cell.style,\n ...cfStyle,\n };\n const evaluatedCell = this.getters.getEvaluatedCell(position);\n if (evaluatedCell.link && !computedStyle.textColor) {\n computedStyle.textColor = LINK_COLOR;\n }\n if (this.getters.isFilterHeader(position)) {\n computedStyle.bold = true;\n }\n return computedStyle;\n }\n getConditionalIcon({ col, row }) {\n var _a;\n const activeSheet = this.getters.getActiveSheetId();\n const icon = this.computedIcons[activeSheet];\n return icon && ((_a = icon[col]) === null || _a === void 0 ? void 0 : _a[row]);\n }\n // ---------------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------------\n /**\n * Compute the styles according to the conditional formatting.\n * This computation must happen after the cell values are computed if they change\n *\n * This result of the computation will be in the state.cell[XC].conditionalStyle and will be the union of all the style\n * properties of the rules applied (in order).\n * So if a cell has multiple conditional formatting applied to it, and each affect a different value of the style,\n * the resulting style will have the combination of all those values.\n * If multiple conditional formatting use the same style value, they will be applied in order so that the last applied wins\n */\n computeStyles() {\n var _a;\n const sheetId = this.getters.getActiveSheetId();\n this.computedStyles[sheetId] = {};\n this.computedIcons[sheetId] = {};\n const computedStyle = this.computedStyles[sheetId];\n for (let cf of this.getters.getConditionalFormats(sheetId).reverse()) {\n try {\n switch (cf.rule.type) {\n case \"ColorScaleRule\":\n for (let range of cf.ranges) {\n this.applyColorScale(range, cf.rule);\n }\n break;\n case \"IconSetRule\":\n for (let range of cf.ranges) {\n this.applyIcon(range, cf.rule);\n }\n break;\n default:\n for (let ref of cf.ranges) {\n const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;\n for (let row = zone.top; row <= zone.bottom; row++) {\n for (let col = zone.left; col <= zone.right; col++) {\n const pr = this.rulePredicate[cf.rule.type];\n let cell = this.getters.getEvaluatedCell({ sheetId, col, row });\n if (pr && pr(cell, cf.rule)) {\n if (!computedStyle[col])\n computedStyle[col] = [];\n // we must combine all the properties of all the CF rules applied to the given cell\n computedStyle[col][row] = Object.assign(((_a = computedStyle[col]) === null || _a === void 0 ? void 0 : _a[row]) || {}, cf.rule.style);\n }\n }\n }\n }\n break;\n }\n }\n catch (_) {\n // we don't care about the errors within the evaluation of a rule\n }\n }\n }\n parsePoint(range, threshold, functionName) {\n const sheetId = this.getters.getActiveSheetId();\n const rangeValues = this.getters\n .getEvaluatedCellsInZone(sheetId, this.getters.getRangeFromSheetXC(sheetId, range).zone)\n .filter((cell) => cell.type === CellValueType.number)\n .map((cell) => cell.value);\n switch (threshold.type) {\n case \"value\":\n const result = functionName === \"max\" ? Math.max(...rangeValues) : Math.min(...rangeValues);\n return result;\n case \"number\":\n return Number(threshold.value);\n case \"percentage\":\n const min = Math.min(...rangeValues);\n const max = Math.max(...rangeValues);\n const delta = max - min;\n return min + (delta * Number(threshold.value)) / 100;\n case \"percentile\":\n return percentile(rangeValues, Number(threshold.value) / 100, true);\n case \"formula\":\n const value = threshold.value && this.getters.evaluateFormula(threshold.value);\n return !(value instanceof Promise) ? value : null;\n default:\n return null;\n }\n }\n applyIcon(range, rule) {\n const lowerInflectionPoint = this.parsePoint(range, rule.lowerInflectionPoint);\n const upperInflectionPoint = this.parsePoint(range, rule.upperInflectionPoint);\n if (lowerInflectionPoint === null ||\n upperInflectionPoint === null ||\n lowerInflectionPoint > upperInflectionPoint) {\n return;\n }\n const sheetId = this.getters.getActiveSheetId();\n const zone = this.getters.getRangeFromSheetXC(sheetId, range).zone;\n const computedIcons = this.computedIcons[sheetId];\n const iconSet = [rule.icons.upper, rule.icons.middle, rule.icons.lower];\n for (let row = zone.top; row <= zone.bottom; row++) {\n for (let col = zone.left; col <= zone.right; col++) {\n const cell = this.getters.getEvaluatedCell({ sheetId, col, row });\n if (cell.type !== CellValueType.number) {\n continue;\n }\n const icon = this.computeIcon(cell.value, upperInflectionPoint, rule.upperInflectionPoint.operator, lowerInflectionPoint, rule.lowerInflectionPoint.operator, iconSet);\n if (!computedIcons[col]) {\n computedIcons[col] = [];\n }\n computedIcons[col][row] = icon;\n }\n }\n }\n computeIcon(value, upperInflectionPoint, upperOperator, lowerInflectionPoint, lowerOperator, icons) {\n if ((upperOperator === \"ge\" && value >= upperInflectionPoint) ||\n (upperOperator === \"gt\" && value > upperInflectionPoint)) {\n return icons[0];\n }\n else if ((lowerOperator === \"ge\" && value >= lowerInflectionPoint) ||\n (lowerOperator === \"gt\" && value > lowerInflectionPoint)) {\n return icons[1];\n }\n return icons[2];\n }\n applyColorScale(range, rule) {\n var _a;\n const minValue = this.parsePoint(range, rule.minimum, \"min\");\n const midValue = rule.midpoint ? this.parsePoint(range, rule.midpoint) : null;\n const maxValue = this.parsePoint(range, rule.maximum, \"max\");\n if (minValue === null ||\n maxValue === null ||\n minValue >= maxValue ||\n (midValue && (minValue >= midValue || midValue >= maxValue))) {\n return;\n }\n const sheetId = this.getters.getActiveSheetId();\n const zone = this.getters.getRangeFromSheetXC(sheetId, range).zone;\n const computedStyle = this.computedStyles[sheetId];\n const colorCellArgs = [];\n if (rule.midpoint && midValue) {\n colorCellArgs.push({\n minValue,\n minColor: rule.minimum.color,\n colorDiffUnit: this.computeColorDiffUnits(minValue, midValue, rule.minimum.color, rule.midpoint.color),\n });\n colorCellArgs.push({\n minValue: midValue,\n minColor: rule.midpoint.color,\n colorDiffUnit: this.computeColorDiffUnits(midValue, maxValue, rule.midpoint.color, rule.maximum.color),\n });\n }\n else {\n colorCellArgs.push({\n minValue,\n minColor: rule.minimum.color,\n colorDiffUnit: this.computeColorDiffUnits(minValue, maxValue, rule.minimum.color, rule.maximum.color),\n });\n }\n for (let row = zone.top; row <= zone.bottom; row++) {\n for (let col = zone.left; col <= zone.right; col++) {\n const cell = this.getters.getEvaluatedCell({ sheetId, col, row });\n if (cell.type === CellValueType.number) {\n const value = clip(cell.value, minValue, maxValue);\n let color;\n if (colorCellArgs.length === 2 && midValue) {\n color =\n value <= midValue\n ? this.colorCell(value, colorCellArgs[0].minValue, colorCellArgs[0].minColor, colorCellArgs[0].colorDiffUnit)\n : this.colorCell(value, colorCellArgs[1].minValue, colorCellArgs[1].minColor, colorCellArgs[1].colorDiffUnit);\n }\n else {\n color = this.colorCell(value, colorCellArgs[0].minValue, colorCellArgs[0].minColor, colorCellArgs[0].colorDiffUnit);\n }\n if (!computedStyle[col])\n computedStyle[col] = [];\n computedStyle[col][row] = ((_a = computedStyle[col]) === null || _a === void 0 ? void 0 : _a[row]) || {};\n computedStyle[col][row].fillColor = colorNumberString(color);\n }\n }\n }\n }\n computeColorDiffUnits(minValue, maxValue, minColor, maxColor) {\n const deltaValue = maxValue - minValue;\n const deltaColorR = ((minColor >> 16) % 256) - ((maxColor >> 16) % 256);\n const deltaColorG = ((minColor >> 8) % 256) - ((maxColor >> 8) % 256);\n const deltaColorB = (minColor % 256) - (maxColor % 256);\n const colorDiffUnitR = deltaColorR / deltaValue;\n const colorDiffUnitG = deltaColorG / deltaValue;\n const colorDiffUnitB = deltaColorB / deltaValue;\n return [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB];\n }\n colorCell(value, minValue, minColor, colorDiffUnit) {\n const [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB] = colorDiffUnit;\n const r = Math.round(((minColor >> 16) % 256) - colorDiffUnitR * (value - minValue));\n const g = Math.round(((minColor >> 8) % 256) - colorDiffUnitG * (value - minValue));\n const b = Math.round((minColor % 256) - colorDiffUnitB * (value - minValue));\n return (r << 16) | (g << 8) | b;\n }\n /**\n * Add or remove cells to a given conditional formatting rule.\n */\n adaptRules(sheetId, cf, toAdd, toRemove) {\n if (toAdd.length === 0 && toRemove.length === 0) {\n return;\n }\n const rules = this.getters.getConditionalFormats(sheetId);\n const replaceIndex = rules.findIndex((c) => c.id === cf.id);\n let currentRanges = [];\n if (replaceIndex > -1) {\n currentRanges = rules[replaceIndex].ranges;\n }\n currentRanges = currentRanges.concat(toAdd);\n const newRangesXC = recomputeZones(currentRanges, toRemove);\n this.dispatch(\"ADD_CONDITIONAL_FORMAT\", {\n cf: {\n id: cf.id,\n rule: cf.rule,\n stopIfTrue: cf.stopIfTrue,\n },\n ranges: newRangesXC.map((xc) => this.getters.getRangeDataFromXc(sheetId, xc)),\n sheetId,\n });\n }\n pasteCf(origin, target, operation) {\n const xc = toXC(target.col, target.row);\n for (let rule of this.getters.getConditionalFormats(origin.sheetId)) {\n for (let range of rule.ranges) {\n if (isInside(origin.col, origin.row, this.getters.getRangeFromSheetXC(origin.sheetId, range).zone)) {\n const cf = rule;\n const toRemoveRange = [];\n if (operation === \"CUT\") {\n //remove from current rule\n toRemoveRange.push(toXC(origin.col, origin.row));\n }\n if (origin.sheetId === target.sheetId) {\n this.adaptRules(origin.sheetId, cf, [xc], toRemoveRange);\n }\n else {\n this.adaptRules(target.sheetId, cf, [xc], []);\n this.adaptRules(origin.sheetId, cf, [], toRemoveRange);\n }\n }\n }\n }\n }\n }\n EvaluationConditionalFormatPlugin.getters = [\"getConditionalIcon\", \"getCellComputedStyle\"];\n\n class FilterEvaluationPlugin extends UIPlugin {\n constructor() {\n super(...arguments);\n this.filterValues = {};\n this.hiddenRows = new Set();\n this.isEvaluationDirty = false;\n }\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"UPDATE_FILTER\":\n if (!this.getters.getFilterId(cmd)) {\n return 79 /* CommandResult.FilterNotFound */;\n }\n break;\n }\n return 0 /* CommandResult.Success */;\n }\n handle(cmd) {\n switch (cmd.type) {\n case \"UNDO\":\n case \"REDO\":\n case \"UPDATE_CELL\":\n case \"EVALUATE_CELLS\":\n case \"ACTIVATE_SHEET\":\n case \"REMOVE_FILTER_TABLE\":\n this.isEvaluationDirty = true;\n break;\n case \"START\":\n for (const sheetId of this.getters.getSheetIds()) {\n this.filterValues[sheetId] = {};\n for (const filter of this.getters.getFilters(sheetId)) {\n this.filterValues[sheetId][filter.id] = [];\n }\n }\n break;\n case \"CREATE_SHEET\":\n this.filterValues[cmd.sheetId] = {};\n break;\n case \"HIDE_COLUMNS_ROWS\":\n this.updateHiddenRows();\n break;\n case \"UPDATE_FILTER\":\n this.updateFilter(cmd);\n this.updateHiddenRows();\n break;\n case \"DUPLICATE_SHEET\":\n const filterValues = {};\n for (const newFilter of this.getters.getFilters(cmd.sheetIdTo)) {\n const zone = newFilter.zoneWithHeaders;\n filterValues[newFilter.id] = this.getFilterValues({\n sheetId: cmd.sheetId,\n col: zone.left,\n row: zone.top,\n });\n }\n this.filterValues[cmd.sheetIdTo] = filterValues;\n break;\n // If we don't handle DELETE_SHEET, on one hand we will have some residual data, on the other hand we keep the data\n // on DELETE_SHEET followed by undo\n }\n }\n finalize() {\n if (this.isEvaluationDirty) {\n this.updateHiddenRows();\n this.isEvaluationDirty = false;\n }\n }\n isRowFiltered(sheetId, row) {\n if (sheetId !== this.getters.getActiveSheetId()) {\n return false;\n }\n return this.hiddenRows.has(row);\n }\n getCellBorderWithFilterBorder(position) {\n const { sheetId, col, row } = position;\n let filterBorder = undefined;\n for (let filters of this.getters.getFilterTables(sheetId)) {\n const zone = filters.zone;\n if (isInside(col, row, zone)) {\n // The borders should be at the edges of the visible zone of the filter\n const visibleZone = this.intersectZoneWithViewport(sheetId, zone);\n filterBorder = {\n top: row === visibleZone.top ? DEFAULT_FILTER_BORDER_DESC : undefined,\n bottom: row === visibleZone.bottom ? DEFAULT_FILTER_BORDER_DESC : undefined,\n left: col === visibleZone.left ? DEFAULT_FILTER_BORDER_DESC : undefined,\n right: col === visibleZone.right ? DEFAULT_FILTER_BORDER_DESC : undefined,\n };\n }\n }\n const cellBorder = this.getters.getCellBorder(position);\n // Use removeFalsyAttributes to avoid overwriting filter borders with undefined values\n const border = { ...filterBorder, ...removeFalsyAttributes(cellBorder || {}) };\n return isObjectEmptyRecursive(border) ? null : border;\n }\n getFilterHeaders(sheetId) {\n const headers = [];\n for (let filters of this.getters.getFilterTables(sheetId)) {\n const zone = filters.zone;\n if (!zone) {\n continue;\n }\n const row = zone.top;\n for (let col = zone.left; col <= zone.right; col++) {\n if (this.getters.isColHidden(sheetId, col) || this.getters.isRowHidden(sheetId, row)) {\n continue;\n }\n headers.push({ col, row });\n }\n }\n return headers;\n }\n getFilterValues(position) {\n const id = this.getters.getFilterId(position);\n const sheetId = position.sheetId;\n if (!id || !this.filterValues[sheetId])\n return [];\n return this.filterValues[sheetId][id] || [];\n }\n isFilterHeader({ sheetId, col, row }) {\n const headers = this.getFilterHeaders(sheetId);\n return headers.some((header) => header.col === col && header.row === row);\n }\n isFilterActive(position) {\n var _a, _b;\n const id = this.getters.getFilterId(position);\n const sheetId = position.sheetId;\n return Boolean(id && ((_b = (_a = this.filterValues[sheetId]) === null || _a === void 0 ? void 0 : _a[id]) === null || _b === void 0 ? void 0 : _b.length));\n }\n intersectZoneWithViewport(sheetId, zone) {\n const colsRange = range(zone.left, zone.right + 1);\n const rowsRange = range(zone.top, zone.bottom + 1);\n return {\n left: this.getters.findVisibleHeader(sheetId, \"COL\", colsRange),\n right: this.getters.findVisibleHeader(sheetId, \"COL\", colsRange.reverse()),\n top: this.getters.findVisibleHeader(sheetId, \"ROW\", rowsRange),\n bottom: this.getters.findVisibleHeader(sheetId, \"ROW\", rowsRange.reverse()),\n };\n }\n updateFilter({ col, row, values, sheetId }) {\n const id = this.getters.getFilterId({ sheetId, col, row });\n if (!id)\n return;\n if (!this.filterValues[sheetId])\n this.filterValues[sheetId] = {};\n this.filterValues[sheetId][id] = values;\n }\n updateHiddenRows() {\n var _a, _b;\n const sheetId = this.getters.getActiveSheetId();\n const filters = this.getters\n .getFilters(sheetId)\n .sort((filter1, filter2) => filter1.zoneWithHeaders.top - filter2.zoneWithHeaders.top);\n const hiddenRows = new Set();\n for (let filter of filters) {\n // Disable filters whose header are hidden\n if (this.getters.isRowHiddenByUser(sheetId, filter.zoneWithHeaders.top))\n continue;\n if (hiddenRows.has(filter.zoneWithHeaders.top))\n continue;\n const filteredValues = (_b = (_a = this.filterValues[sheetId]) === null || _a === void 0 ? void 0 : _a[filter.id]) === null || _b === void 0 ? void 0 : _b.map(toLowerCase);\n if (!filteredValues || !filter.filteredZone)\n continue;\n for (let row = filter.filteredZone.top; row <= filter.filteredZone.bottom; row++) {\n const value = this.getCellValueAsString(sheetId, filter.col, row);\n if (filteredValues.includes(value)) {\n hiddenRows.add(row);\n }\n }\n }\n this.hiddenRows = hiddenRows;\n }\n getCellValueAsString(sheetId, col, row) {\n const value = this.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue;\n return value.toLowerCase();\n }\n exportForExcel(data) {\n for (const sheetData of data.sheets) {\n for (const tableData of sheetData.filterTables) {\n const tableZone = toZone(tableData.range);\n const filters = [];\n const headerNames = [];\n for (const i of range(0, zoneToDimension(tableZone).width)) {\n const position = {\n sheetId: sheetData.id,\n col: tableZone.left + i,\n row: tableZone.top,\n };\n const filteredValues = this.getFilterValues(position);\n const filter = this.getters.getFilter(position);\n if (!filter)\n continue;\n const valuesInFilterZone = filter.filteredZone\n ? positions(filter.filteredZone)\n .map(({ col, row }) => this.getters.getEvaluatedCell({ sheetId: sheetData.id, col, row }))\n .filter((cell) => cell.type !== CellValueType.empty)\n .map((cell) => cell.formattedValue)\n : [];\n // In xlsx, filtered values = values that are displayed, not values that are hidden\n const xlsxFilteredValues = valuesInFilterZone.filter((val) => !filteredValues.includes(val));\n filters.push({ colId: i, filteredValues: [...new Set(xlsxFilteredValues)] });\n // In xlsx, filter header should ALWAYS be a string and should be unique\n const headerPosition = {\n col: filter.col,\n row: filter.zoneWithHeaders.top,\n sheetId: sheetData.id,\n };\n const headerString = this.getters.getEvaluatedCell(headerPosition).formattedValue;\n const headerName = this.getUniqueColNameForExcel(i, headerString, headerNames);\n headerNames.push(headerName);\n sheetData.cells[toXC(headerPosition.col, headerPosition.row)] = {\n ...sheetData.cells[toXC(headerPosition.col, headerPosition.row)],\n content: headerName,\n value: headerName,\n isFormula: false,\n };\n }\n tableData.filters = filters;\n }\n }\n }\n /**\n * Get an unique column name for the column at colIndex. If the column name is already in the array of used column names,\n * concatenate a number to the name until we find a new unique name (eg. \"ColName\" => \"ColName1\" => \"ColName2\" ...)\n */\n getUniqueColNameForExcel(colIndex, colName, usedColNames) {\n if (!colName) {\n colName = `Column${colIndex}`;\n }\n let currentColName = colName;\n let i = 2;\n while (usedColNames.includes(currentColName)) {\n currentColName = colName + String(i);\n i++;\n }\n return currentColName;\n }\n }\n FilterEvaluationPlugin.getters = [\n \"getCellBorderWithFilterBorder\",\n \"getFilterHeaders\",\n \"getFilterValues\",\n \"isFilterHeader\",\n \"isRowFiltered\",\n \"isFilterActive\",\n ];\n\n class InternalViewport {\n constructor(getters, sheetId, boundaries, sizeInGrid, options, offsets) {\n this.getters = getters;\n this.sheetId = sheetId;\n this.boundaries = boundaries;\n this.width = sizeInGrid.width;\n this.height = sizeInGrid.height;\n this.offsetScrollbarX = offsets.x;\n this.offsetScrollbarY = offsets.y;\n this.canScrollVertically = options.canScrollVertically;\n this.canScrollHorizontally = options.canScrollHorizontally;\n this.offsetCorrectionX = this.getters.getColDimensions(this.sheetId, this.boundaries.left).start;\n this.offsetCorrectionY = this.getters.getRowDimensions(this.sheetId, this.boundaries.top).start;\n this.adjustViewportOffsetX();\n this.adjustViewportOffsetY();\n }\n // PUBLIC\n getMaxSize() {\n const lastCol = this.getters.findLastVisibleColRowIndex(this.sheetId, \"COL\", {\n first: this.boundaries.left,\n last: this.boundaries.right,\n });\n const lastRow = this.getters.findLastVisibleColRowIndex(this.sheetId, \"ROW\", {\n first: this.boundaries.top,\n last: this.boundaries.bottom,\n });\n const { end: lastColEnd, size: lastColSize } = this.getters.getColDimensions(this.sheetId, lastCol);\n const { end: lastRowEnd, size: lastRowSize } = this.getters.getRowDimensions(this.sheetId, lastRow);\n const leftColIndex = this.searchHeaderIndex(\"COL\", lastColEnd - this.width, 0);\n const leftColSize = this.getters.getColSize(this.sheetId, leftColIndex);\n const leftRowIndex = this.searchHeaderIndex(\"ROW\", lastRowEnd - this.height, 0);\n const topRowSize = this.getters.getRowSize(this.sheetId, leftRowIndex);\n const width = lastColEnd -\n this.offsetCorrectionX +\n (this.canScrollHorizontally\n ? Math.max(DEFAULT_CELL_WIDTH, Math.min(leftColSize, this.width - lastColSize))\n : 0);\n const height = lastRowEnd -\n this.offsetCorrectionY +\n (this.canScrollVertically\n ? Math.max(DEFAULT_CELL_HEIGHT + 5, Math.min(topRowSize, this.height - lastRowSize))\n : 0);\n return { width, height };\n }\n /**\n * Return the index of a column given an offset x, based on the pane left\n * visible cell.\n * It returns -1 if no column is found.\n */\n getColIndex(x, absolute = false) {\n if (x < this.offsetCorrectionX || x > this.offsetCorrectionX + this.width) {\n return -1;\n }\n return this.searchHeaderIndex(\"COL\", x - this.offsetCorrectionX, this.left, absolute);\n }\n /**\n * Return the index of a row given an offset y, based on the pane top\n * visible cell.\n * It returns -1 if no row is found.\n */\n getRowIndex(y, absolute = false) {\n if (y < this.offsetCorrectionY || y > this.offsetCorrectionY + this.height) {\n return -1;\n }\n return this.searchHeaderIndex(\"ROW\", y - this.offsetCorrectionY, this.top, absolute);\n }\n /**\n * This function will make sure that the provided cell position (or current selected position) is part of\n * the pane that is actually displayed on the client. We therefore adjust the offset of the pane\n * until it contains the cell completely.\n */\n adjustPosition(position) {\n const sheetId = this.sheetId;\n if (!position) {\n position = this.getters.getSheetPosition(sheetId);\n }\n const mainCellPosition = this.getters.getMainCellPosition({ sheetId, ...position });\n const { col, row } = this.getters.getNextVisibleCellPosition(mainCellPosition);\n if (isInside(col, this.boundaries.top, this.boundaries)) {\n this.adjustPositionX(col);\n }\n if (isInside(this.boundaries.left, row, this.boundaries)) {\n this.adjustPositionY(row);\n }\n }\n adjustPositionX(col) {\n const sheetId = this.sheetId;\n const { start, end } = this.getters.getColDimensions(sheetId, col);\n while (end > this.offsetX + this.offsetCorrectionX + this.width &&\n this.offsetX + this.offsetCorrectionX < start) {\n this.offsetX = this.getters.getColDimensions(sheetId, this.left).end - this.offsetCorrectionX;\n this.offsetScrollbarX = this.offsetX;\n this.adjustViewportZoneX();\n }\n while (col < this.left) {\n let leftCol;\n for (leftCol = this.left; leftCol >= 0; leftCol--) {\n if (!this.getters.isColHidden(sheetId, leftCol)) {\n break;\n }\n }\n this.offsetX =\n this.getters.getColDimensions(sheetId, leftCol - 1).start - this.offsetCorrectionX;\n this.offsetScrollbarX = this.offsetX;\n this.adjustViewportZoneX();\n }\n }\n adjustPositionY(row) {\n const sheetId = this.sheetId;\n while (this.getters.getRowDimensions(sheetId, row).end >\n this.offsetY + this.height + this.offsetCorrectionY &&\n this.offsetY + this.offsetCorrectionY < this.getters.getRowDimensions(sheetId, row).start) {\n this.offsetY = this.getters.getRowDimensions(sheetId, this.top).end - this.offsetCorrectionY;\n this.offsetScrollbarY = this.offsetY;\n this.adjustViewportZoneY();\n }\n while (row < this.top) {\n let topRow;\n for (topRow = this.top; topRow >= 0; topRow--) {\n if (!this.getters.isRowHidden(sheetId, topRow)) {\n break;\n }\n }\n this.offsetY =\n this.getters.getRowDimensions(sheetId, topRow - 1).start - this.offsetCorrectionY;\n this.offsetScrollbarY = this.offsetY;\n this.adjustViewportZoneY();\n }\n }\n setViewportOffset(offsetX, offsetY) {\n this.setViewportOffsetX(offsetX);\n this.setViewportOffsetY(offsetY);\n }\n adjustViewportZone() {\n this.adjustViewportZoneX();\n this.adjustViewportZoneY();\n }\n /**\n *\n * @param zone\n * @returns Computes the absolute coordinate of a given zone inside the viewport\n */\n getRect(zone) {\n const targetZone = intersection(zone, this.zone);\n if (targetZone) {\n const x = this.getters.getColRowOffset(\"COL\", this.zone.left, targetZone.left) +\n this.offsetCorrectionX;\n const y = this.getters.getColRowOffset(\"ROW\", this.zone.top, targetZone.top) + this.offsetCorrectionY;\n const width = Math.min(this.getters.getColRowOffset(\"COL\", targetZone.left, targetZone.right + 1), this.width);\n const height = Math.min(this.getters.getColRowOffset(\"ROW\", targetZone.top, targetZone.bottom + 1), this.height);\n return {\n x,\n y,\n width,\n height,\n };\n }\n else {\n return undefined;\n }\n }\n isVisible(col, row) {\n const isInside = row <= this.bottom && row >= this.top && col >= this.left && col <= this.right;\n return (isInside &&\n !this.getters.isColHidden(this.sheetId, col) &&\n !this.getters.isRowHidden(this.sheetId, row));\n }\n // PRIVATE\n searchHeaderIndex(dimension, position, startIndex = 0, absolute = false) {\n let size = 0;\n const sheetId = this.sheetId;\n const headers = this.getters.getNumberHeaders(sheetId, dimension);\n for (let i = startIndex; i <= headers - 1; i++) {\n const isHiddenInViewport = !absolute && dimension === \"COL\"\n ? i < this.left && i > this.right\n : i < this.top && i > this.bottom;\n if (this.getters.isHeaderHidden(sheetId, dimension, i) || isHiddenInViewport) {\n continue;\n }\n size +=\n dimension === \"COL\"\n ? this.getters.getColSize(sheetId, i)\n : this.getters.getRowSize(sheetId, i);\n if (size > position) {\n return i;\n }\n }\n return -1;\n }\n get zone() {\n return { left: this.left, right: this.right, top: this.top, bottom: this.bottom };\n }\n setViewportOffsetX(offsetX) {\n if (!this.canScrollHorizontally) {\n return;\n }\n this.offsetScrollbarX = offsetX;\n this.adjustViewportZoneX();\n }\n setViewportOffsetY(offsetY) {\n if (!this.canScrollVertically) {\n return;\n }\n this.offsetScrollbarY = offsetY;\n this.adjustViewportZoneY();\n }\n /** Corrects the viewport's horizontal offset based on the current structure\n * To make sure that at least on column is visible inside the viewport.\n */\n adjustViewportOffsetX() {\n if (this.canScrollHorizontally) {\n const { width: viewportWidth } = this.getMaxSize();\n if (this.width + this.offsetScrollbarX > viewportWidth) {\n this.offsetScrollbarX = Math.max(0, viewportWidth - this.width);\n }\n }\n this.left = this.getColIndex(this.offsetScrollbarX, true);\n this.right = this.getColIndex(this.offsetScrollbarX + this.width, true);\n if (this.right === -1) {\n this.right = this.boundaries.right;\n }\n this.adjustViewportZoneX();\n }\n /** Corrects the viewport's vertical offset based on the current structure\n * To make sure that at least on row is visible inside the viewport.\n */\n adjustViewportOffsetY() {\n if (this.canScrollVertically) {\n const { height: paneHeight } = this.getMaxSize();\n if (this.height + this.offsetScrollbarY > paneHeight) {\n this.offsetScrollbarY = Math.max(0, paneHeight - this.height);\n }\n }\n this.top = this.getRowIndex(this.offsetScrollbarY, true);\n this.bottom = this.getRowIndex(this.offsetScrollbarY + this.width, true);\n if (this.bottom === -1) {\n this.bottom = this.boundaries.bottom;\n }\n this.adjustViewportZoneY();\n }\n /** Updates the pane zone and snapped offset based on its horizontal\n * offset (will find Left) and its width (will find Right) */\n adjustViewportZoneX() {\n const sheetId = this.sheetId;\n this.left = this.searchHeaderIndex(\"COL\", this.offsetScrollbarX, this.boundaries.left);\n this.right = Math.min(this.boundaries.right, this.searchHeaderIndex(\"COL\", this.width, this.left));\n if (this.right === -1) {\n this.right = this.getters.getNumberCols(sheetId) - 1;\n }\n this.offsetX =\n this.getters.getColDimensions(sheetId, this.left).start -\n this.getters.getColDimensions(sheetId, this.boundaries.left).start;\n }\n /** Updates the pane zone and snapped offset based on its vertical\n * offset (will find Top) and its width (will find Bottom) */\n adjustViewportZoneY() {\n const sheetId = this.sheetId;\n this.top = this.searchHeaderIndex(\"ROW\", this.offsetScrollbarY, this.boundaries.top);\n this.bottom = Math.min(this.boundaries.bottom, this.searchHeaderIndex(\"ROW\", this.height, this.top));\n if (this.bottom === -1) {\n this.bottom = this.getters.getNumberRows(sheetId) - 1;\n }\n this.offsetY =\n this.getters.getRowDimensions(sheetId, this.top).start -\n this.getters.getRowDimensions(sheetId, this.boundaries.top).start;\n }\n }\n\n /**\n * EdgeScrollCases Schema\n *\n * The dots/double dots represent a freeze (= a split of viewports)\n * In this example, we froze vertically between columns D and E\n * and horizontally between rows 4 and 5.\n *\n * One can see that we scrolled horizontally from column E to G and\n * vertically from row 5 to 7.\n *\n * A B C D G H I J K L M N O P Q R S T\n * _______________________________________________________\n * 1 | : |\n * 2 | : |\n * 3 | : B \u2191 6 |\n * 4 | : | | | |\n * \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7+\u00b7\u00b7\u00b7+\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7+\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7|\n * 7 | : | | | |\n * 8 | : \u2193 2 | |\n * 9 | : | |\n * 10 | A --+--\u2192 | |\n * 11 | : | |\n * 12 | : | |\n * 13 | \u2190--+-- 1 | |\n * 14 | : | 3 --+--\u2192\n * 15 | : | |\n * 16 | : | |\n * 17 | 5 --+-------------------------------------------+--\u2192\n * 18 | : | |\n * 19 | : 4 | |\n * 20 | : | | |\n * ______________________________+___________| ____________\n * | |\n * \u2193 \u2193\n */\n /**\n * Viewport plugin.\n *\n * This plugin manages all things related to all viewport states.\n *\n */\n class SheetViewPlugin extends UIPlugin {\n constructor() {\n super(...arguments);\n this.viewports = {};\n /**\n * The viewport dimensions are usually set by one of the components\n * (i.e. when grid component is mounted) to properly reflect its state in the DOM.\n * In the absence of a component (standalone model), is it mandatory to set reasonable default values\n * to ensure the correct operation of this plugin.\n */\n this.sheetViewWidth = DEFAULT_SHEETVIEW_SIZE;\n this.sheetViewHeight = DEFAULT_SHEETVIEW_SIZE;\n this.gridOffsetX = 0;\n this.gridOffsetY = 0;\n this.sheetsWithDirtyViewports = new Set();\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"SET_VIEWPORT_OFFSET\":\n return this.checkScrollingDirection(cmd);\n case \"RESIZE_SHEETVIEW\":\n return this.chainValidations(this.checkValuesAreDifferent, this.checkPositiveDimension)(cmd);\n default:\n return 0 /* CommandResult.Success */;\n }\n }\n handleEvent(event) {\n switch (event.type) {\n case \"HeadersSelected\":\n case \"AlterZoneCorner\":\n break;\n case \"ZonesSelected\":\n let { col, row } = findCellInNewZone(event.previousAnchor.zone, event.anchor.zone);\n if (event.mode === \"updateAnchor\") {\n const oldZone = event.previousAnchor.zone;\n const newZone = event.anchor.zone;\n // altering a zone should not move the viewport in a dimension that wasn't changed\n const { top, bottom, left, right } = this.getters.getActiveMainViewport();\n if (oldZone.left === newZone.left && oldZone.right === newZone.right) {\n col = left > col || col > right ? left : col;\n }\n if (oldZone.top === newZone.top && oldZone.bottom === newZone.bottom) {\n row = top > row || row > bottom ? top : row;\n }\n }\n const sheetId = this.getters.getActiveSheetId();\n col = Math.min(col, this.getters.getNumberCols(sheetId) - 1);\n row = Math.min(row, this.getters.getNumberRows(sheetId) - 1);\n this.refreshViewport(this.getters.getActiveSheetId(), { col, row });\n break;\n }\n }\n handle(cmd) {\n var _a;\n this.cleanViewports();\n switch (cmd.type) {\n case \"START\":\n this.selection.observe(this, {\n handleEvent: this.handleEvent.bind(this),\n });\n this.resetViewports(this.getters.getActiveSheetId());\n break;\n case \"UNDO\":\n case \"REDO\":\n this.resetSheetViews();\n break;\n case \"RESIZE_SHEETVIEW\":\n this.resizeSheetView(cmd.height, cmd.width, cmd.gridOffsetX, cmd.gridOffsetY);\n break;\n case \"SET_VIEWPORT_OFFSET\":\n this.setSheetViewOffset(cmd.offsetX, cmd.offsetY);\n break;\n case \"SHIFT_VIEWPORT_DOWN\":\n const { top } = this.getActiveMainViewport();\n const sheetId = this.getters.getActiveSheetId();\n const shiftedOffsetY = this.clipOffsetY(this.getters.getRowDimensions(sheetId, top).start + this.sheetViewHeight);\n this.shiftVertically(shiftedOffsetY);\n break;\n case \"SHIFT_VIEWPORT_UP\": {\n const { top } = this.getActiveMainViewport();\n const sheetId = this.getters.getActiveSheetId();\n const shiftedOffsetY = this.clipOffsetY(this.getters.getRowDimensions(sheetId, top).end - this.sheetViewHeight);\n this.shiftVertically(shiftedOffsetY);\n break;\n }\n case \"REMOVE_COLUMNS_ROWS\":\n case \"RESIZE_COLUMNS_ROWS\":\n case \"HIDE_COLUMNS_ROWS\":\n case \"ADD_COLUMNS_ROWS\":\n case \"UNHIDE_COLUMNS_ROWS\":\n case \"UPDATE_FILTER\":\n this.resetViewports(cmd.sheetId);\n break;\n case \"UPDATE_CELL\":\n // update cell content or format can change hidden rows because of data filters\n if (\"content\" in cmd || \"format\" in cmd || ((_a = cmd.style) === null || _a === void 0 ? void 0 : _a.fontSize) !== undefined) {\n this.sheetsWithDirtyViewports.add(cmd.sheetId);\n }\n break;\n case \"ACTIVATE_SHEET\":\n this.setViewports();\n this.refreshViewport(cmd.sheetIdTo);\n break;\n case \"UNFREEZE_ROWS\":\n case \"UNFREEZE_COLUMNS\":\n case \"FREEZE_COLUMNS\":\n case \"FREEZE_ROWS\":\n case \"UNFREEZE_COLUMNS_ROWS\":\n this.resetViewports(this.getters.getActiveSheetId());\n break;\n case \"DELETE_SHEET\":\n this.sheetsWithDirtyViewports.delete(cmd.sheetId);\n break;\n case \"START_EDITION\":\n const { col, row } = this.getters.getActivePosition();\n this.refreshViewport(this.getters.getActiveSheetId(), { col, row });\n break;\n }\n }\n finalize() {\n for (const sheetId of this.sheetsWithDirtyViewports) {\n this.resetViewports(sheetId);\n }\n this.sheetsWithDirtyViewports = new Set();\n this.setViewports();\n }\n setViewports() {\n var _a;\n const sheetIds = this.getters.getSheetIds();\n for (const sheetId of sheetIds) {\n if (!((_a = this.viewports[sheetId]) === null || _a === void 0 ? void 0 : _a.bottomRight)) {\n this.resetViewports(sheetId);\n }\n }\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n /**\n * Return the index of a column given an offset x, based on the viewport left\n * visible cell.\n * It returns -1 if no column is found.\n */\n getColIndex(x) {\n const sheetId = this.getters.getActiveSheetId();\n return Math.max(...this.getSubViewports(sheetId).map((viewport) => viewport.getColIndex(x)));\n }\n /**\n * Return the index of a row given an offset y, based on the viewport top\n * visible cell.\n * It returns -1 if no row is found.\n */\n getRowIndex(y) {\n const sheetId = this.getters.getActiveSheetId();\n return Math.max(...this.getSubViewports(sheetId).map((viewport) => viewport.getRowIndex(y)));\n }\n getSheetViewDimensionWithHeaders() {\n return {\n width: this.sheetViewWidth + this.gridOffsetX,\n height: this.sheetViewHeight + this.gridOffsetY,\n };\n }\n getSheetViewDimension() {\n return {\n width: this.sheetViewWidth,\n height: this.sheetViewHeight,\n };\n }\n /** type as pane, not viewport but basically pane extends viewport */\n getActiveMainViewport() {\n const sheetId = this.getters.getActiveSheetId();\n return this.getMainViewport(sheetId);\n }\n /**\n * Return the scroll info of the active sheet, ie. the offset between the viewport left/top side and\n * the grid left/top side, snapped to the columns/rows.\n */\n getActiveSheetScrollInfo() {\n const sheetId = this.getters.getActiveSheetId();\n const viewport = this.getMainInternalViewport(sheetId);\n return {\n scrollX: viewport.offsetX,\n scrollY: viewport.offsetY,\n };\n }\n /**\n * Return the DOM scroll info of the active sheet, ie. the offset between the viewport left/top side and\n * the grid left/top side, corresponding to the scroll of the scrollbars and not snapped to the grid.\n */\n getActiveSheetDOMScrollInfo() {\n const sheetId = this.getters.getActiveSheetId();\n const viewport = this.getMainInternalViewport(sheetId);\n return {\n scrollX: viewport.offsetScrollbarX,\n scrollY: viewport.offsetScrollbarY,\n };\n }\n getSheetViewVisibleCols() {\n const sheetId = this.getters.getActiveSheetId();\n const viewports = this.getSubViewports(sheetId);\n return [...new Set(viewports.map((v) => range(v.left, v.right + 1)).flat())].filter((col) => !this.getters.isHeaderHidden(sheetId, \"COL\", col));\n }\n getSheetViewVisibleRows() {\n const sheetId = this.getters.getActiveSheetId();\n const viewports = this.getSubViewports(sheetId);\n return [...new Set(viewports.map((v) => range(v.top, v.bottom + 1)).flat())].filter((row) => !this.getters.isHeaderHidden(sheetId, \"ROW\", row));\n }\n /**\n * Return the main viewport maximum size. That is the zone dimension\n * with some bottom and right padding.\n */\n getMainViewportRect() {\n const sheetId = this.getters.getActiveSheetId();\n const viewport = this.getMainInternalViewport(sheetId);\n const { xSplit, ySplit } = this.getters.getPaneDivisions(sheetId);\n let { width, height } = viewport.getMaxSize();\n const x = this.getters.getColDimensions(sheetId, xSplit).start;\n const y = this.getters.getRowDimensions(sheetId, ySplit).start;\n return { x, y, width, height };\n }\n getMaximumSheetOffset() {\n const sheetId = this.getters.getActiveSheetId();\n const { width, height } = this.getMainViewportRect();\n const viewport = this.getMainInternalViewport(sheetId);\n return {\n maxOffsetX: Math.max(0, width - viewport.width + 1),\n maxOffsetY: Math.max(0, height - viewport.height + 1),\n };\n }\n getColRowOffsetInViewport(dimension, referenceIndex, index) {\n const sheetId = this.getters.getActiveSheetId();\n const visibleCols = this.getters.getSheetViewVisibleCols();\n const visibleRows = this.getters.getSheetViewVisibleRows();\n if (index < referenceIndex) {\n return -this.getColRowOffsetInViewport(dimension, index, referenceIndex);\n }\n let offset = 0;\n const visibleIndexes = dimension === \"COL\" ? visibleCols : visibleRows;\n for (let i = referenceIndex; i < index; i++) {\n if (!visibleIndexes.includes(i)) {\n continue;\n }\n offset +=\n dimension === \"COL\"\n ? this.getters.getColSize(sheetId, i)\n : this.getters.getRowSize(sheetId, i);\n }\n return offset;\n }\n /**\n * Check if a given position is visible in the viewport.\n */\n isVisibleInViewport({ sheetId, col, row }) {\n return this.getSubViewports(sheetId).some((pane) => pane.isVisible(col, row));\n }\n // => return s the new offset\n getEdgeScrollCol(x, previousX, startingX) {\n let canEdgeScroll = false;\n let direction = 0;\n let delay = 0;\n /** 4 cases : See EdgeScrollCases Schema at the top\n * 1. previous in XRight > XLeft\n * 3. previous in XRight > outside\n * 5. previous in Left > outside\n * A. previous in Left > right\n * with X a position taken in the bottomRIght (aka scrollable) viewport\n */\n const { xSplit } = this.getters.getPaneDivisions(this.getters.getActiveSheetId());\n const { width } = this.getSheetViewDimension();\n const { x: offsetCorrectionX } = this.getMainViewportCoordinates();\n const currentOffsetX = this.getActiveSheetScrollInfo().scrollX;\n if (x > width) {\n // 3 & 5\n canEdgeScroll = true;\n delay = scrollDelay(x - width);\n direction = 1;\n }\n else if (x < offsetCorrectionX && startingX >= offsetCorrectionX && currentOffsetX > 0) {\n // 1\n canEdgeScroll = true;\n delay = scrollDelay(offsetCorrectionX - x);\n direction = -1;\n }\n else if (xSplit && previousX < offsetCorrectionX && x > offsetCorrectionX) {\n // A\n canEdgeScroll = true;\n delay = scrollDelay(x);\n direction = \"reset\";\n }\n return { canEdgeScroll, direction, delay };\n }\n getEdgeScrollRow(y, previousY, tartingY) {\n let canEdgeScroll = false;\n let direction = 0;\n let delay = 0;\n /** 4 cases : See EdgeScrollCases Schema at the top\n * 2. previous in XBottom > XTop\n * 4. previous in XRight > outside\n * 6. previous in Left > outside\n * B. previous in Left > right\n * with X a position taken in the bottomRIght (aka scrollable) viewport\n */\n const { ySplit } = this.getters.getPaneDivisions(this.getters.getActiveSheetId());\n const { height } = this.getSheetViewDimension();\n const { y: offsetCorrectionY } = this.getMainViewportCoordinates();\n const currentOffsetY = this.getActiveSheetScrollInfo().scrollY;\n if (y > height) {\n // 4 & 6\n canEdgeScroll = true;\n delay = scrollDelay(y - height);\n direction = 1;\n }\n else if (y < offsetCorrectionY && tartingY >= offsetCorrectionY && currentOffsetY > 0) {\n // 2\n canEdgeScroll = true;\n delay = scrollDelay(offsetCorrectionY - y);\n direction = -1;\n }\n else if (ySplit && previousY < offsetCorrectionY && y > offsetCorrectionY) {\n // B\n canEdgeScroll = true;\n delay = scrollDelay(y);\n direction = \"reset\";\n }\n return { canEdgeScroll, direction, delay };\n }\n /**\n * Computes the coordinates and size to draw the zone on the canvas\n */\n getVisibleRect(zone) {\n const sheetId = this.getters.getActiveSheetId();\n const viewportRects = this.getSubViewports(sheetId)\n .map((viewport) => viewport.getRect(zone))\n .filter(isDefined$1);\n if (viewportRects.length === 0) {\n return { x: 0, y: 0, width: 0, height: 0 };\n }\n const x = Math.min(...viewportRects.map((rect) => rect.x));\n const y = Math.min(...viewportRects.map((rect) => rect.y));\n const width = Math.max(...viewportRects.map((rect) => rect.x + rect.width)) - x;\n const height = Math.max(...viewportRects.map((rect) => rect.y + rect.height)) - y;\n return {\n x: x + this.gridOffsetX,\n y: y + this.gridOffsetY,\n width,\n height,\n };\n }\n /**\n * Returns the position of the MainViewport relatively to the start of the grid (without headers)\n * It corresponds to the summed dimensions of the visible cols/rows (in x/y respectively)\n * situated before the pane divisions.\n */\n getMainViewportCoordinates() {\n const sheetId = this.getters.getActiveSheetId();\n const { xSplit, ySplit } = this.getters.getPaneDivisions(sheetId);\n const x = this.getters.getColDimensions(sheetId, xSplit).start;\n const y = this.getters.getRowDimensions(sheetId, ySplit).start;\n return { x, y };\n }\n // ---------------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------------\n ensureMainViewportExist(sheetId) {\n if (!this.viewports[sheetId]) {\n this.resetViewports(sheetId);\n }\n }\n getSubViewports(sheetId) {\n this.ensureMainViewportExist(sheetId);\n return Object.values(this.viewports[sheetId]).filter(isDefined$1);\n }\n checkPositiveDimension(cmd) {\n if (cmd.width < 0 || cmd.height < 0) {\n return 68 /* CommandResult.InvalidViewportSize */;\n }\n return 0 /* CommandResult.Success */;\n }\n checkValuesAreDifferent(cmd) {\n const { height, width } = this.getSheetViewDimension();\n if (cmd.gridOffsetX === this.gridOffsetX &&\n cmd.gridOffsetY === this.gridOffsetY &&\n cmd.width === width &&\n cmd.height === height) {\n return 76 /* CommandResult.ValuesNotChanged */;\n }\n return 0 /* CommandResult.Success */;\n }\n checkScrollingDirection({ offsetX, offsetY, }) {\n const pane = this.getMainInternalViewport(this.getters.getActiveSheetId());\n if ((!pane.canScrollHorizontally && offsetX > 0) ||\n (!pane.canScrollVertically && offsetY > 0)) {\n return 69 /* CommandResult.InvalidScrollingDirection */;\n }\n return 0 /* CommandResult.Success */;\n }\n getMainViewport(sheetId) {\n const viewport = this.getMainInternalViewport(sheetId);\n return {\n top: viewport.top,\n left: viewport.left,\n bottom: viewport.bottom,\n right: viewport.right,\n };\n }\n getMainInternalViewport(sheetId) {\n this.ensureMainViewportExist(sheetId);\n return this.viewports[sheetId].bottomRight;\n }\n /** gets rid of deprecated sheetIds */\n cleanViewports() {\n const sheetIds = this.getters.getSheetIds();\n for (let sheetId of Object.keys(this.viewports)) {\n if (!sheetIds.includes(sheetId)) {\n delete this.viewports[sheetId];\n }\n }\n }\n resetSheetViews() {\n for (let sheetId of Object.keys(this.viewports)) {\n const position = this.getters.getSheetPosition(sheetId);\n this.resetViewports(sheetId);\n const viewports = this.getSubViewports(sheetId);\n Object.values(viewports).forEach((viewport) => {\n viewport.adjustPosition(position);\n });\n }\n }\n resizeSheetView(height, width, gridOffsetX = 0, gridOffsetY = 0) {\n this.sheetViewHeight = height;\n this.sheetViewWidth = width;\n this.gridOffsetX = gridOffsetX;\n this.gridOffsetY = gridOffsetY;\n this.recomputeViewports();\n }\n recomputeViewports() {\n for (let sheetId of Object.keys(this.viewports)) {\n this.resetViewports(sheetId);\n }\n }\n setSheetViewOffset(offsetX, offsetY) {\n const sheetId = this.getters.getActiveSheetId();\n const { maxOffsetX, maxOffsetY } = this.getMaximumSheetOffset();\n Object.values(this.getSubViewports(sheetId)).forEach((viewport) => viewport.setViewportOffset(clip(offsetX, 0, maxOffsetX), clip(offsetY, 0, maxOffsetY)));\n }\n /**\n * Clip the vertical offset within the allowed range.\n * Not above the sheet, nor below the sheet.\n */\n clipOffsetY(offsetY) {\n const { height } = this.getMainViewportRect();\n const maxOffset = height - this.sheetViewHeight;\n offsetY = Math.min(offsetY, maxOffset);\n offsetY = Math.max(offsetY, 0);\n return offsetY;\n }\n getViewportOffset(sheetId) {\n var _a, _b;\n return {\n x: ((_a = this.viewports[sheetId]) === null || _a === void 0 ? void 0 : _a.bottomRight.offsetScrollbarX) || 0,\n y: ((_b = this.viewports[sheetId]) === null || _b === void 0 ? void 0 : _b.bottomRight.offsetScrollbarY) || 0,\n };\n }\n resetViewports(sheetId) {\n if (!this.getters.tryGetSheet(sheetId)) {\n return;\n }\n const { xSplit, ySplit } = this.getters.getPaneDivisions(sheetId);\n const nCols = this.getters.getNumberCols(sheetId);\n const nRows = this.getters.getNumberRows(sheetId);\n const colOffset = this.getters.getColRowOffset(\"COL\", 0, xSplit, sheetId);\n const rowOffset = this.getters.getColRowOffset(\"ROW\", 0, ySplit, sheetId);\n const { xRatio, yRatio } = this.getFrozenSheetViewRatio(sheetId);\n const canScrollHorizontally = xRatio < 1.0;\n const canScrollVertically = yRatio < 1.0;\n const previousOffset = this.getViewportOffset(sheetId);\n const sheetViewports = {\n topLeft: (ySplit &&\n xSplit &&\n new InternalViewport(this.getters, sheetId, { left: 0, right: xSplit - 1, top: 0, bottom: ySplit - 1 }, { width: colOffset, height: rowOffset }, { canScrollHorizontally: false, canScrollVertically: false }, { x: 0, y: 0 })) ||\n undefined,\n topRight: (ySplit &&\n new InternalViewport(this.getters, sheetId, { left: xSplit, right: nCols - 1, top: 0, bottom: ySplit - 1 }, { width: this.sheetViewWidth - colOffset, height: rowOffset }, { canScrollHorizontally, canScrollVertically: false }, { x: canScrollHorizontally ? previousOffset.x : 0, y: 0 })) ||\n undefined,\n bottomLeft: (xSplit &&\n new InternalViewport(this.getters, sheetId, { left: 0, right: xSplit - 1, top: ySplit, bottom: nRows - 1 }, { width: colOffset, height: this.sheetViewHeight - rowOffset }, { canScrollHorizontally: false, canScrollVertically }, { x: 0, y: canScrollVertically ? previousOffset.y : 0 })) ||\n undefined,\n bottomRight: new InternalViewport(this.getters, sheetId, { left: xSplit, right: nCols - 1, top: ySplit, bottom: nRows - 1 }, {\n width: this.sheetViewWidth - colOffset,\n height: this.sheetViewHeight - rowOffset,\n }, { canScrollHorizontally, canScrollVertically }, {\n x: canScrollHorizontally ? previousOffset.x : 0,\n y: canScrollVertically ? previousOffset.y : 0,\n }),\n };\n this.viewports[sheetId] = sheetViewports;\n }\n /**\n * Adjust the viewport such that the anchor position is visible\n */\n refreshViewport(sheetId, anchorPosition) {\n Object.values(this.getSubViewports(sheetId)).forEach((viewport) => {\n viewport.adjustViewportZone();\n viewport.adjustPosition(anchorPosition);\n });\n }\n /**\n * Shift the viewport vertically and move the selection anchor\n * such that it remains at the same place relative to the\n * viewport top.\n */\n shiftVertically(offset) {\n const { top } = this.getActiveMainViewport();\n const { scrollX } = this.getActiveSheetScrollInfo();\n this.setSheetViewOffset(scrollX, offset);\n const { anchor } = this.getters.getSelection();\n const deltaRow = this.getActiveMainViewport().top - top;\n this.selection.selectCell(anchor.cell.col, anchor.cell.row + deltaRow);\n }\n getVisibleFigures() {\n const sheetId = this.getters.getActiveSheetId();\n const result = [];\n const figures = this.getters.getFigures(sheetId);\n const { scrollX, scrollY } = this.getActiveSheetScrollInfo();\n const { x: offsetCorrectionX, y: offsetCorrectionY } = this.getters.getMainViewportCoordinates();\n const { width, height } = this.getters.getSheetViewDimensionWithHeaders();\n for (const figure of figures) {\n if (figure.x >= offsetCorrectionX &&\n (figure.x + figure.width <= offsetCorrectionX + scrollX ||\n figure.x >= width + scrollX + offsetCorrectionX)) {\n continue;\n }\n if (figure.y >= offsetCorrectionY &&\n (figure.y + figure.height <= offsetCorrectionY + scrollY ||\n figure.y >= height + scrollY + offsetCorrectionY)) {\n continue;\n }\n result.push(figure);\n }\n return result;\n }\n getFrozenSheetViewRatio(sheetId) {\n const { xSplit, ySplit } = this.getters.getPaneDivisions(sheetId);\n const offsetCorrectionX = this.getters.getColDimensions(sheetId, xSplit).start;\n const offsetCorrectionY = this.getters.getRowDimensions(sheetId, ySplit).start;\n const width = this.sheetViewWidth + this.gridOffsetX;\n const height = this.sheetViewHeight + this.gridOffsetY;\n return { xRatio: offsetCorrectionX / width, yRatio: offsetCorrectionY / height };\n }\n }\n SheetViewPlugin.getters = [\n \"getColIndex\",\n \"getRowIndex\",\n \"getActiveMainViewport\",\n \"getSheetViewDimension\",\n \"getSheetViewDimensionWithHeaders\",\n \"getMainViewportRect\",\n \"isVisibleInViewport\",\n \"getEdgeScrollCol\",\n \"getEdgeScrollRow\",\n \"getVisibleFigures\",\n \"getVisibleRect\",\n \"getColRowOffsetInViewport\",\n \"getMainViewportCoordinates\",\n \"getActiveSheetScrollInfo\",\n \"getActiveSheetDOMScrollInfo\",\n \"getSheetViewVisibleCols\",\n \"getSheetViewVisibleRows\",\n \"getFrozenSheetViewRatio\",\n ];\n\n /**\n * This plugin manage the autofill.\n *\n * The way it works is the next one:\n * For each line (row if the direction is left/right, col otherwise), we create\n * a \"AutofillGenerator\" object which is used to compute the cells to\n * autofill.\n *\n * When we need to autofill a cell, we compute the origin cell in the source.\n * EX: from A1:A2, autofill A3->A6.\n * Target | Origin cell\n * A3 | A1\n * A4 | A2\n * A5 | A1\n * A6 | A2\n * When we have the origin, we take the associated cell in the AutofillGenerator\n * and we apply the modifier (AutofillModifier) associated to the content of the\n * cell.\n */\n /**\n * This class is used to generate the next values to autofill.\n * It's done from a selection (the source) and describe how the next values\n * should be computed.\n */\n class AutofillGenerator {\n constructor(cells, getters, direction) {\n this.index = 0;\n this.cells = cells;\n this.getters = getters;\n this.direction = direction;\n }\n /**\n * Get the next value to autofill\n */\n next() {\n const genCell = this.cells[this.index++ % this.cells.length];\n const rule = genCell.rule;\n const { cellData, tooltip } = autofillModifiersRegistry\n .get(rule.type)\n .apply(rule, genCell.data, this.getters, this.direction);\n return {\n cellData,\n tooltip,\n origin: {\n col: genCell.data.col,\n row: genCell.data.row,\n },\n };\n }\n }\n /**\n * Autofill Plugin\n *\n */\n class AutofillPlugin extends UIPlugin {\n constructor() {\n super(...arguments);\n this.lastCellSelected = {};\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"AUTOFILL_SELECT\":\n const sheetId = this.getters.getActiveSheetId();\n this.lastCellSelected.col =\n cmd.col === -1\n ? this.lastCellSelected.col\n : clip(cmd.col, 0, this.getters.getNumberCols(sheetId));\n this.lastCellSelected.row =\n cmd.row === -1\n ? this.lastCellSelected.row\n : clip(cmd.row, 0, this.getters.getNumberRows(sheetId));\n if (this.lastCellSelected.col !== undefined && this.lastCellSelected.row !== undefined) {\n return 0 /* CommandResult.Success */;\n }\n return 45 /* CommandResult.InvalidAutofillSelection */;\n case \"AUTOFILL_AUTO\":\n const zone = this.getters.getSelectedZone();\n return zone.top === zone.bottom\n ? 0 /* CommandResult.Success */\n : 1 /* CommandResult.CancelledForUnknownReason */;\n }\n return 0 /* CommandResult.Success */;\n }\n handle(cmd) {\n switch (cmd.type) {\n case \"AUTOFILL\":\n this.autofill(true);\n break;\n case \"AUTOFILL_SELECT\":\n this.select(cmd.col, cmd.row);\n break;\n case \"AUTOFILL_AUTO\":\n this.autofillAuto();\n break;\n case \"AUTOFILL_CELL\":\n this.autoFillMerge(cmd.originCol, cmd.originRow, cmd.col, cmd.row);\n const sheetId = this.getters.getActiveSheetId();\n this.dispatch(\"UPDATE_CELL\", {\n sheetId,\n col: cmd.col,\n row: cmd.row,\n style: cmd.style || null,\n content: cmd.content || \"\",\n format: cmd.format || \"\",\n });\n this.dispatch(\"SET_BORDER\", {\n sheetId,\n col: cmd.col,\n row: cmd.row,\n border: cmd.border,\n });\n }\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n getAutofillTooltip() {\n return this.tooltip;\n }\n // ---------------------------------------------------------------------------\n // Private methods\n // ---------------------------------------------------------------------------\n /**\n * Autofill the autofillZone from the current selection\n * @param apply Flag set to true to apply the autofill in the model. It's\n * useful to set it to false when we need to fill the tooltip\n */\n autofill(apply) {\n if (!this.autofillZone || !this.steps || this.direction === undefined) {\n this.tooltip = undefined;\n return;\n }\n const source = this.getters.getSelectedZone();\n const target = this.autofillZone;\n switch (this.direction) {\n case \"down\" /* DIRECTION.DOWN */:\n for (let col = source.left; col <= source.right; col++) {\n const xcs = [];\n for (let row = source.top; row <= source.bottom; row++) {\n xcs.push(toXC(col, row));\n }\n const generator = this.createGenerator(xcs);\n for (let row = target.top; row <= target.bottom; row++) {\n this.computeNewCell(generator, col, row, apply);\n }\n }\n break;\n case \"up\" /* DIRECTION.UP */:\n for (let col = source.left; col <= source.right; col++) {\n const xcs = [];\n for (let row = source.bottom; row >= source.top; row--) {\n xcs.push(toXC(col, row));\n }\n const generator = this.createGenerator(xcs);\n for (let row = target.bottom; row >= target.top; row--) {\n this.computeNewCell(generator, col, row, apply);\n }\n }\n break;\n case \"left\" /* DIRECTION.LEFT */:\n for (let row = source.top; row <= source.bottom; row++) {\n const xcs = [];\n for (let col = source.right; col >= source.left; col--) {\n xcs.push(toXC(col, row));\n }\n const generator = this.createGenerator(xcs);\n for (let col = target.right; col >= target.left; col--) {\n this.computeNewCell(generator, col, row, apply);\n }\n }\n break;\n case \"right\" /* DIRECTION.RIGHT */:\n for (let row = source.top; row <= source.bottom; row++) {\n const xcs = [];\n for (let col = source.left; col <= source.right; col++) {\n xcs.push(toXC(col, row));\n }\n const generator = this.createGenerator(xcs);\n for (let col = target.left; col <= target.right; col++) {\n this.computeNewCell(generator, col, row, apply);\n }\n }\n break;\n }\n if (apply) {\n this.autofillZone = undefined;\n this.selection.resizeAnchorZone(this.direction, this.steps);\n this.lastCellSelected = {};\n this.direction = undefined;\n this.steps = 0;\n this.tooltip = undefined;\n }\n }\n /**\n * Select a cell which becomes the last cell of the autofillZone\n */\n select(col, row) {\n const source = this.getters.getSelectedZone();\n if (isInside(col, row, source)) {\n this.autofillZone = undefined;\n return;\n }\n this.direction = this.getDirection(col, row);\n switch (this.direction) {\n case \"up\" /* DIRECTION.UP */:\n this.saveZone(row, source.top - 1, source.left, source.right);\n this.steps = source.top - row;\n break;\n case \"down\" /* DIRECTION.DOWN */:\n this.saveZone(source.bottom + 1, row, source.left, source.right);\n this.steps = row - source.bottom;\n break;\n case \"left\" /* DIRECTION.LEFT */:\n this.saveZone(source.top, source.bottom, col, source.left - 1);\n this.steps = source.left - col;\n break;\n case \"right\" /* DIRECTION.RIGHT */:\n this.saveZone(source.top, source.bottom, source.right + 1, col);\n this.steps = col - source.right;\n break;\n }\n this.autofill(false);\n }\n /**\n * Computes the autofillZone to autofill when the user double click on the\n * autofiller\n */\n autofillAuto() {\n const zone = this.getters.getSelectedZone();\n const sheetId = this.getters.getActiveSheetId();\n let col = zone.left;\n let row = zone.bottom;\n if (col > 0) {\n let left = this.getters.getEvaluatedCell({ sheetId, col: col - 1, row });\n while (left.type !== CellValueType.empty) {\n row += 1;\n left = this.getters.getEvaluatedCell({ sheetId, col: col - 1, row });\n }\n }\n if (row === zone.bottom) {\n col = zone.right;\n if (col <= this.getters.getNumberCols(sheetId)) {\n let right = this.getters.getEvaluatedCell({ sheetId, col: col + 1, row });\n while (right.type !== CellValueType.empty) {\n row += 1;\n right = this.getters.getEvaluatedCell({ sheetId, col: col + 1, row });\n }\n }\n }\n if (row !== zone.bottom) {\n this.select(zone.left, row - 1);\n this.autofill(true);\n }\n }\n /**\n * Generate the next cell\n */\n computeNewCell(generator, col, row, apply) {\n const { cellData, tooltip, origin } = generator.next();\n const { content, style, border, format } = cellData;\n this.tooltip = tooltip;\n if (apply) {\n this.dispatch(\"AUTOFILL_CELL\", {\n originCol: origin.col,\n originRow: origin.row,\n col,\n row,\n content,\n style,\n border,\n format,\n });\n }\n }\n /**\n * Get the rule associated to the current cell\n */\n getRule(cell, cells) {\n const rules = autofillRulesRegistry.getAll().sort((a, b) => a.sequence - b.sequence);\n const rule = rules.find((rule) => rule.condition(cell, cells));\n return rule && rule.generateRule(cell, cells);\n }\n /**\n * Create the generator to be able to autofill the next cells.\n */\n createGenerator(source) {\n const nextCells = [];\n const cellsData = [];\n const sheetId = this.getters.getActiveSheetId();\n for (let xc of source) {\n const { col, row } = toCartesian(xc);\n const cell = this.getters.getCell({ sheetId, col, row });\n cellsData.push({\n col,\n row,\n cell,\n sheetId,\n });\n }\n const cells = cellsData.map((cellData) => cellData.cell);\n for (let cellData of cellsData) {\n let rule = { type: \"COPY_MODIFIER\" };\n if (cellData && cellData.cell) {\n const newRule = this.getRule(cellData.cell, cells);\n rule = newRule || rule;\n }\n const border = this.getters.getCellBorder(cellData) || undefined;\n nextCells.push({\n data: { ...cellData, border },\n rule,\n });\n }\n return new AutofillGenerator(nextCells, this.getters, this.direction);\n }\n saveZone(top, bottom, left, right) {\n this.autofillZone = { top, bottom, left, right };\n }\n /**\n * Compute the direction of the autofill from the last selected zone and\n * a given cell (col, row)\n */\n getDirection(col, row) {\n const source = this.getters.getSelectedZone();\n const position = {\n up: { number: source.top - row, value: \"up\" /* DIRECTION.UP */ },\n down: { number: row - source.bottom, value: \"down\" /* DIRECTION.DOWN */ },\n left: { number: source.left - col, value: \"left\" /* DIRECTION.LEFT */ },\n right: { number: col - source.right, value: \"right\" /* DIRECTION.RIGHT */ },\n };\n if (Object.values(position)\n .map((x) => (x.number > 0 ? 1 : 0))\n .reduce((acc, value) => acc + value) === 1) {\n return Object.values(position).find((x) => (x.number > 0 ? 1 : 0)).value;\n }\n const first = position.up.number > 0 ? \"up\" : \"down\";\n const second = position.left.number > 0 ? \"left\" : \"right\";\n return Math.abs(position[first].number) >= Math.abs(position[second].number)\n ? position[first].value\n : position[second].value;\n }\n autoFillMerge(originCol, originRow, col, row) {\n const sheetId = this.getters.getActiveSheetId();\n const position = { sheetId, col, row };\n const originPosition = { sheetId, col: originCol, row: originRow };\n if (this.getters.isInMerge(position) && !this.getters.isInMerge(originPosition)) {\n const zone = this.getters.getMerge(position);\n if (zone) {\n this.dispatch(\"REMOVE_MERGE\", {\n sheetId,\n target: [zone],\n });\n }\n }\n const originMerge = this.getters.getMerge(originPosition);\n if ((originMerge === null || originMerge === void 0 ? void 0 : originMerge.topLeft.col) === originCol && (originMerge === null || originMerge === void 0 ? void 0 : originMerge.topLeft.row) === originRow) {\n this.dispatch(\"ADD_MERGE\", {\n sheetId,\n target: [\n {\n top: row,\n bottom: row + originMerge.bottom - originMerge.top,\n left: col,\n right: col + originMerge.right - originMerge.left,\n },\n ],\n });\n }\n }\n // ---------------------------------------------------------------------------\n // Grid rendering\n // ---------------------------------------------------------------------------\n drawGrid(renderingContext) {\n if (!this.autofillZone) {\n return;\n }\n const { ctx, thinLineWidth } = renderingContext;\n const { x, y, width, height } = this.getters.getVisibleRect(this.autofillZone);\n if (width > 0 && height > 0) {\n ctx.strokeStyle = \"black\";\n ctx.lineWidth = thinLineWidth;\n ctx.setLineDash([3]);\n ctx.strokeRect(x, y, width, height);\n ctx.setLineDash([]);\n }\n }\n }\n AutofillPlugin.layers = [5 /* LAYERS.Autofill */];\n AutofillPlugin.getters = [\"getAutofillTooltip\"];\n\n class AutomaticSumPlugin extends UIPlugin {\n handle(cmd) {\n switch (cmd.type) {\n case \"SUM_SELECTION\":\n const sheetId = this.getters.getActiveSheetId();\n const { zones, anchor } = this.getters.getSelection();\n for (const zone of zones) {\n const sums = this.getAutomaticSums(sheetId, zone, anchor.cell);\n this.dispatchCellUpdates(sheetId, sums);\n }\n break;\n }\n }\n getAutomaticSums(sheetId, zone, anchor) {\n return this.shouldFindData(sheetId, zone)\n ? this.sumAdjacentData(sheetId, zone, anchor)\n : this.sumData(sheetId, zone);\n }\n // ---------------------------------------------------------------------------\n // Private methods\n // ---------------------------------------------------------------------------\n sumData(sheetId, zone) {\n const dimensions = this.dimensionsToSum(sheetId, zone);\n const sums = this.sumDimensions(sheetId, zone, dimensions).filter(({ zone }) => !this.getters.isEmpty(sheetId, zone));\n if (dimensions.has(\"ROW\") && dimensions.has(\"COL\")) {\n sums.push(this.sumTotal(zone));\n }\n return sums;\n }\n sumAdjacentData(sheetId, zone, anchor) {\n const { col, row } = isInside(anchor.col, anchor.row, zone)\n ? anchor\n : { col: zone.left, row: zone.top };\n const dataZone = this.findAdjacentData(sheetId, col, row);\n if (!dataZone) {\n return [];\n }\n if (this.getters.isSingleCellOrMerge(sheetId, zone) ||\n isOneDimensional(union(dataZone, zone))) {\n return [{ position: { col, row }, zone: dataZone }];\n }\n else {\n return this.sumDimensions(sheetId, union(dataZone, zone), this.transpose(this.dimensionsToSum(sheetId, zone)));\n }\n }\n /**\n * Find a zone to automatically sum a column or row of numbers.\n *\n * We first decide which direction will be summed (column or row).\n * Here is the strategy:\n * 1. If the left cell is a number and the top cell is not: choose horizontal\n * 2. Try to find a valid vertical zone. If it's valid: choose vertical\n * 3. Try to find a valid horizontal zone. If it's valid: choose horizontal\n * 4. Otherwise, no zone is returned\n *\n * Now, how to find a valid zone?\n * The zone starts directly above or on the left of the starting point\n * (depending on the direction).\n * The zone ends where the first continuous sequence of numbers ends.\n * Empty or text cells can be part of the zone while no number has been found.\n * Other kind of cells (boolean, dates, etc.) are not valid in the zone and the\n * search stops immediately if one is found.\n *\n * ------- -------\n * | 1 | | 1 |\n * ------- -------\n * | | | |\n * ------- <= end of the sequence, stop here -------\n * | 2 | | 2 |\n * ------- -------\n * | 3 | <= start of the number sequence | 3 |\n * ------- -------\n * | | <= ignored | FALSE | <= invalid, no zone is found\n * ------- -------\n * | A | <= ignored | A | <= ignored\n * ------- -------\n */\n findAdjacentData(sheetId, col, row) {\n const sheet = this.getters.getSheet(sheetId);\n const mainCellPosition = this.getters.getMainCellPosition({ sheetId, col, row });\n const zone = this.findSuitableZoneToSum(sheet, mainCellPosition.col, mainCellPosition.row);\n if (zone) {\n return this.getters.expandZone(sheetId, zone);\n }\n return undefined;\n }\n /**\n * Return the zone to sum if a valid one is found.\n * @see getAutomaticSumZone\n */\n findSuitableZoneToSum(sheet, col, row) {\n const topCell = this.getters.getEvaluatedCell({ sheetId: sheet.id, col, row: row - 1 });\n const leftCell = this.getters.getEvaluatedCell({ sheetId: sheet.id, col: col - 1, row });\n if (this.isNumber(leftCell) && !this.isNumber(topCell)) {\n return this.findHorizontalZone(sheet, col, row);\n }\n const verticalZone = this.findVerticalZone(sheet, col, row);\n if (this.isZoneValid(verticalZone)) {\n return verticalZone;\n }\n const horizontalZone = this.findHorizontalZone(sheet, col, row);\n if (this.isZoneValid(horizontalZone)) {\n return horizontalZone;\n }\n return undefined;\n }\n findVerticalZone(sheet, col, row) {\n const zone = {\n top: 0,\n bottom: row - 1,\n left: col,\n right: col,\n };\n const top = this.reduceZoneStart(sheet, zone, zone.bottom);\n return { ...zone, top };\n }\n findHorizontalZone(sheet, col, row) {\n const zone = {\n top: row,\n bottom: row,\n left: 0,\n right: col - 1,\n };\n const left = this.reduceZoneStart(sheet, zone, zone.right);\n return { ...zone, left };\n }\n /**\n * Reduces a column or row zone to a valid zone for the automatic sum.\n * @see getAutomaticSumZone\n * @param sheet\n * @param zone one dimensional zone (a single row or a single column). The zone is\n * assumed to start at the beginning of the column (top=0) or the row (left=0)\n * @param end end index of the zone (`bottom` or `right` depending on the dimension)\n * @returns the starting position of the valid zone or Infinity if the zone is not valid.\n */\n reduceZoneStart(sheet, zone, end) {\n const cells = this.getters.getEvaluatedCellsInZone(sheet.id, zone);\n const cellPositions = range(end, -1, -1);\n const invalidCells = cellPositions.filter((position) => cells[position] && !cells[position].isAutoSummable);\n const maxValidPosition = Math.max(...invalidCells);\n const numberSequences = groupConsecutive(cellPositions.filter((position) => this.isNumber(cells[position])));\n const firstSequence = numberSequences[0] || [];\n if (Math.max(...firstSequence) < maxValidPosition) {\n return Infinity;\n }\n return Math.min(...firstSequence);\n }\n shouldFindData(sheetId, zone) {\n return this.getters.isEmpty(sheetId, zone) || this.getters.isSingleCellOrMerge(sheetId, zone);\n }\n isNumber(cell) {\n var _a;\n return cell.type === CellValueType.number && !((_a = cell.format) === null || _a === void 0 ? void 0 : _a.match(DATETIME_FORMAT));\n }\n isZoneValid(zone) {\n return zone.bottom >= zone.top && zone.right >= zone.left;\n }\n lastColIsEmpty(sheetId, zone) {\n return this.getters.isEmpty(sheetId, { ...zone, left: zone.right });\n }\n lastRowIsEmpty(sheetId, zone) {\n return this.getters.isEmpty(sheetId, { ...zone, top: zone.bottom });\n }\n /**\n * Decides which dimensions (columns or rows) should be summed\n * based on its shape and what's inside the zone.\n */\n dimensionsToSum(sheetId, zone) {\n const dimensions = new Set();\n if (isOneDimensional(zone)) {\n dimensions.add(zoneToDimension(zone).width === 1 ? \"COL\" : \"ROW\");\n return dimensions;\n }\n if (this.lastColIsEmpty(sheetId, zone)) {\n dimensions.add(\"ROW\");\n }\n if (this.lastRowIsEmpty(sheetId, zone)) {\n dimensions.add(\"COL\");\n }\n if (dimensions.size === 0) {\n dimensions.add(\"COL\");\n }\n return dimensions;\n }\n /**\n * Sum each column and/or row in the zone in the appropriate cells,\n * depending on the available space.\n */\n sumDimensions(sheetId, zone, dimensions) {\n return [\n ...(dimensions.has(\"COL\") ? this.sumColumns(zone, sheetId) : []),\n ...(dimensions.has(\"ROW\") ? this.sumRows(zone, sheetId) : []),\n ];\n }\n /**\n * Sum the total of the zone in the bottom right cell, assuming\n * the last row contains summed columns.\n */\n sumTotal(zone) {\n const { bottom, right } = zone;\n return {\n position: { col: right, row: bottom },\n zone: { ...zone, top: bottom, right: right - 1 },\n };\n }\n sumColumns(zone, sheetId) {\n const target = this.nextEmptyRow(sheetId, { ...zone, bottom: zone.bottom - 1 });\n zone = { ...zone, bottom: Math.min(zone.bottom, target.bottom - 1) };\n return positions(target).map((position) => ({\n position,\n zone: { ...zone, right: position.col, left: position.col },\n }));\n }\n sumRows(zone, sheetId) {\n const target = this.nextEmptyCol(sheetId, { ...zone, right: zone.right - 1 });\n zone = { ...zone, right: Math.min(zone.right, target.right - 1) };\n return positions(target).map((position) => ({\n position,\n zone: { ...zone, top: position.row, bottom: position.row },\n }));\n }\n dispatchCellUpdates(sheetId, sums) {\n for (const sum of sums) {\n const { col, row } = sum.position;\n this.dispatch(\"UPDATE_CELL\", {\n sheetId,\n col,\n row,\n content: `=SUM(${this.getters.zoneToXC(sheetId, sum.zone)})`,\n });\n }\n }\n /**\n * Find the first row where all cells below the zone are empty.\n */\n nextEmptyRow(sheetId, zone) {\n let start = zone.bottom + 1;\n const { left, right } = zone;\n while (!this.getters.isEmpty(sheetId, { bottom: start, top: start, left, right })) {\n start++;\n }\n return {\n ...zone,\n top: start,\n bottom: start,\n };\n }\n /**\n * Find the first column where all cells right of the zone are empty.\n */\n nextEmptyCol(sheetId, zone) {\n let start = zone.right + 1;\n const { top, bottom } = zone;\n while (!this.getters.isEmpty(sheetId, { left: start, right: start, top, bottom })) {\n start++;\n }\n return {\n ...zone,\n left: start,\n right: start,\n };\n }\n /**\n * Transpose the given dimensions.\n * COL becomes ROW\n * ROW becomes COL\n */\n transpose(dimensions) {\n return new Set([...dimensions.values()].map((dimension) => (dimension === \"COL\" ? \"ROW\" : \"COL\")));\n }\n }\n AutomaticSumPlugin.getters = [\"getAutomaticSums\"];\n\n /**\n * Plugin managing the display of components next to cells.\n */\n class CellPopoverPlugin extends UIPlugin {\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"OPEN_CELL_POPOVER\":\n try {\n cellPopoverRegistry.get(cmd.popoverType);\n }\n catch (error) {\n return 72 /* CommandResult.InvalidCellPopover */;\n }\n return 0 /* CommandResult.Success */;\n default:\n return 0 /* CommandResult.Success */;\n }\n }\n handle(cmd) {\n switch (cmd.type) {\n case \"ACTIVATE_SHEET\":\n this.persistentPopover = undefined;\n break;\n case \"OPEN_CELL_POPOVER\":\n this.persistentPopover = {\n col: cmd.col,\n row: cmd.row,\n sheetId: this.getters.getActiveSheetId(),\n type: cmd.popoverType,\n };\n break;\n case \"CLOSE_CELL_POPOVER\":\n this.persistentPopover = undefined;\n break;\n }\n }\n getCellPopover({ col, row }) {\n var _a, _b;\n const sheetId = this.getters.getActiveSheetId();\n if (this.persistentPopover && this.getters.isVisibleInViewport(this.persistentPopover)) {\n const position = this.getters.getMainCellPosition(this.persistentPopover);\n const popover = (_b = (_a = cellPopoverRegistry\n .get(this.persistentPopover.type)).onOpen) === null || _b === void 0 ? void 0 : _b.call(_a, position, this.getters);\n return !(popover === null || popover === void 0 ? void 0 : popover.isOpen)\n ? { isOpen: false }\n : {\n ...popover,\n anchorRect: this.computePopoverAnchorRect(this.persistentPopover),\n };\n }\n if (col === undefined ||\n row === undefined ||\n !this.getters.isVisibleInViewport({ sheetId, col, row })) {\n return { isOpen: false };\n }\n const position = this.getters.getMainCellPosition({ sheetId, col, row });\n const popover = cellPopoverRegistry\n .getAll()\n .map((matcher) => { var _a; return (_a = matcher.onHover) === null || _a === void 0 ? void 0 : _a.call(matcher, position, this.getters); })\n .find((popover) => popover === null || popover === void 0 ? void 0 : popover.isOpen);\n return !(popover === null || popover === void 0 ? void 0 : popover.isOpen)\n ? { isOpen: false }\n : {\n ...popover,\n anchorRect: this.computePopoverAnchorRect(position),\n };\n }\n hasOpenedPopover() {\n return this.persistentPopover !== undefined;\n }\n getPersistentPopoverTypeAtPosition({ col, row }) {\n if (this.persistentPopover &&\n this.persistentPopover.col === col &&\n this.persistentPopover.row === row) {\n return this.persistentPopover.type;\n }\n return undefined;\n }\n computePopoverAnchorRect({ col, row }) {\n const sheetId = this.getters.getActiveSheetId();\n const merge = this.getters.getMerge({ sheetId, col, row });\n if (merge) {\n return this.getters.getVisibleRect(merge);\n }\n return this.getters.getVisibleRect(positionToZone({ col, row }));\n }\n }\n CellPopoverPlugin.getters = [\n \"getCellPopover\",\n \"getPersistentPopoverTypeAtPosition\",\n \"hasOpenedPopover\",\n ];\n\n const BORDER_COLOR = \"#8B008B\";\n const BACKGROUND_COLOR = \"#8B008B33\";\n var Direction;\n (function (Direction) {\n Direction[Direction[\"previous\"] = -1] = \"previous\";\n Direction[Direction[\"current\"] = 0] = \"current\";\n Direction[Direction[\"next\"] = 1] = \"next\";\n })(Direction || (Direction = {}));\n /**\n * Find and Replace Plugin\n *\n * This plugin is used in combination with the find_and_replace sidePanel\n * It is used to 'highlight' cells that match an input string according to\n * the given searchOptions. The second part of this plugin makes it possible\n * (again with the find_and_replace sidePanel), to replace the values that match\n * the search with a new value.\n */\n class FindAndReplacePlugin extends UIPlugin {\n constructor() {\n super(...arguments);\n this.searchMatches = [];\n this.selectedMatchIndex = null;\n this.currentSearchRegex = null;\n this.searchOptions = {\n matchCase: false,\n exactMatch: false,\n searchFormulas: false,\n };\n this.toSearch = \"\";\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n handle(cmd) {\n switch (cmd.type) {\n case \"UPDATE_SEARCH\":\n this.updateSearch(cmd.toSearch, cmd.searchOptions);\n break;\n case \"CLEAR_SEARCH\":\n this.clearSearch();\n break;\n case \"SELECT_SEARCH_PREVIOUS_MATCH\":\n this.selectNextCell(Direction.previous);\n break;\n case \"SELECT_SEARCH_NEXT_MATCH\":\n this.selectNextCell(Direction.next);\n break;\n case \"REPLACE_SEARCH\":\n this.replace(cmd.replaceWith);\n break;\n case \"REPLACE_ALL_SEARCH\":\n this.replaceAll(cmd.replaceWith);\n break;\n case \"UNDO\":\n case \"REDO\":\n case \"REMOVE_COLUMNS_ROWS\":\n case \"ADD_COLUMNS_ROWS\":\n this.clearSearch();\n break;\n case \"ACTIVATE_SHEET\":\n case \"REFRESH_SEARCH\":\n this.refreshSearch();\n break;\n }\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n getSearchMatches() {\n return this.searchMatches;\n }\n getCurrentSelectedMatchIndex() {\n return this.selectedMatchIndex;\n }\n // ---------------------------------------------------------------------------\n // Search\n // ---------------------------------------------------------------------------\n /**\n * Will update the current searchOptions and accordingly update the regex.\n * It will then search for matches using the regex and store them.\n */\n updateSearch(toSearch, searchOptions) {\n this.searchOptions = searchOptions;\n if (toSearch !== this.toSearch) {\n this.selectedMatchIndex = null;\n }\n this.toSearch = toSearch;\n this.updateRegex();\n this.refreshSearch();\n }\n /**\n * refresh the matches according to the current search options\n */\n refreshSearch() {\n const matches = this.findMatches();\n this.searchMatches = matches;\n this.selectNextCell(Direction.current);\n }\n /**\n * Updates the regex based on the current searchOptions and\n * the value toSearch\n */\n updateRegex() {\n let searchValue = escapeRegExp(this.toSearch);\n const flags = !this.searchOptions.matchCase ? \"i\" : \"\";\n if (this.searchOptions.exactMatch) {\n searchValue = `^${searchValue}$`;\n }\n this.currentSearchRegex = RegExp(searchValue, flags);\n }\n /**\n * Find matches using the current regex\n */\n findMatches() {\n const sheetId = this.getters.getActiveSheetId();\n const cells = this.getters.getCells(sheetId);\n const matches = [];\n if (this.toSearch) {\n for (const cell of Object.values(cells)) {\n const { col, row } = this.getters.getCellPosition(cell.id);\n if (cell &&\n this.currentSearchRegex &&\n this.currentSearchRegex.test(this.getSearchableString({ sheetId, col, row }))) {\n const match = { col, row, selected: false };\n matches.push(match);\n }\n }\n }\n return matches.sort(this.sortByRowThenColumn);\n }\n sortByRowThenColumn(a, b) {\n if (a.row === b.row) {\n return a.col - b.col;\n }\n return a.row > b.row ? 1 : -1;\n }\n /**\n * Changes the selected search cell. Given a direction it will\n * Change the selection to the previous, current or nextCell,\n * if it exists otherwise it will set the selectedMatchIndex to null.\n * It will also reset the index to 0 if the search has changed.\n * It is also used to keep coherence between the selected searchMatch\n * and selectedMatchIndex.\n */\n selectNextCell(indexChange) {\n const matches = this.searchMatches;\n if (!matches.length) {\n this.selectedMatchIndex = null;\n return;\n }\n let nextIndex;\n if (this.selectedMatchIndex === null) {\n nextIndex = 0;\n }\n else {\n nextIndex = this.selectedMatchIndex + indexChange;\n }\n //modulo of negative value to be able to cycle in both directions with previous and next\n nextIndex = ((nextIndex % matches.length) + matches.length) % matches.length;\n this.selectedMatchIndex = nextIndex;\n this.selection.selectCell(matches[nextIndex].col, matches[nextIndex].row);\n for (let index = 0; index < this.searchMatches.length; index++) {\n this.searchMatches[index].selected = index === this.selectedMatchIndex;\n }\n }\n clearSearch() {\n this.toSearch = \"\";\n this.searchMatches = [];\n this.selectedMatchIndex = null;\n this.currentSearchRegex = null;\n this.searchOptions = {\n matchCase: false,\n exactMatch: false,\n searchFormulas: false,\n };\n }\n // ---------------------------------------------------------------------------\n // Replace\n // ---------------------------------------------------------------------------\n /**\n * Replace the value of the currently selected match\n */\n replace(replaceWith) {\n if (this.selectedMatchIndex === null || !this.currentSearchRegex) {\n return;\n }\n const matches = this.searchMatches;\n const selectedMatch = matches[this.selectedMatchIndex];\n const sheetId = this.getters.getActiveSheetId();\n const cell = this.getters.getCell({ sheetId, ...selectedMatch });\n if ((cell === null || cell === void 0 ? void 0 : cell.isFormula) && !this.searchOptions.searchFormulas) {\n this.selectNextCell(Direction.next);\n }\n else {\n const replaceRegex = new RegExp(this.currentSearchRegex.source, this.currentSearchRegex.flags + \"g\");\n const toReplace = this.getSearchableString({\n sheetId,\n col: selectedMatch.col,\n row: selectedMatch.row,\n });\n const newContent = toReplace.replace(replaceRegex, replaceWith);\n this.dispatch(\"UPDATE_CELL\", {\n sheetId: this.getters.getActiveSheetId(),\n col: selectedMatch.col,\n row: selectedMatch.row,\n content: newContent,\n });\n this.searchMatches.splice(this.selectedMatchIndex, 1);\n this.selectNextCell(Direction.current);\n }\n }\n /**\n * Apply the replace function to all the matches one time.\n */\n replaceAll(replaceWith) {\n const matchCount = this.searchMatches.length;\n for (let i = 0; i < matchCount; i++) {\n this.replace(replaceWith);\n }\n }\n getSearchableString(position) {\n const cell = this.getters.getCell(position);\n if (this.searchOptions.searchFormulas && (cell === null || cell === void 0 ? void 0 : cell.isFormula)) {\n return cell.content;\n }\n return this.getters.getEvaluatedCell(position).formattedValue;\n }\n // ---------------------------------------------------------------------------\n // Grid rendering\n // ---------------------------------------------------------------------------\n drawGrid(renderingContext) {\n const { ctx } = renderingContext;\n const sheetId = this.getters.getActiveSheetId();\n for (const match of this.searchMatches) {\n const merge = this.getters.getMerge({ sheetId, col: match.col, row: match.row });\n const left = merge ? merge.left : match.col;\n const right = merge ? merge.right : match.col;\n const top = merge ? merge.top : match.row;\n const bottom = merge ? merge.bottom : match.row;\n const { x, y, width, height } = this.getters.getVisibleRect({ top, left, right, bottom });\n if (width > 0 && height > 0) {\n ctx.fillStyle = BACKGROUND_COLOR;\n ctx.fillRect(x, y, width, height);\n if (match.selected) {\n ctx.strokeStyle = BORDER_COLOR;\n ctx.strokeRect(x, y, width, height);\n }\n }\n }\n }\n }\n FindAndReplacePlugin.layers = [3 /* LAYERS.Search */];\n FindAndReplacePlugin.getters = [\"getSearchMatches\", \"getCurrentSelectedMatchIndex\"];\n\n class FormatPlugin extends UIPlugin {\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n handle(cmd) {\n switch (cmd.type) {\n case \"SET_DECIMAL\":\n this.setDecimal(cmd.sheetId, cmd.target, cmd.step);\n break;\n }\n }\n /**\n * This function allows to adjust the quantity of decimal places after a decimal\n * point on cells containing number value. It does this by changing the cells\n * format. Values aren't modified.\n *\n * The change of the decimal quantity is done one by one, the sign of the step\n * variable indicates whether we are increasing or decreasing.\n *\n * If several cells are in the zone, the format resulting from the change of the\n * first cell (with number type) will be applied to the whole zone.\n */\n setDecimal(sheetId, zones, step) {\n // Find the first cell with a number value and get the format\n const numberFormat = this.searchNumberFormat(sheetId, zones);\n if (numberFormat !== undefined) {\n // Depending on the step sign, increase or decrease the decimal representation\n // of the format\n const newFormat = changeDecimalPlaces(numberFormat, step);\n // Apply the new format on the whole zone\n this.dispatch(\"SET_FORMATTING\", {\n sheetId,\n target: zones,\n format: newFormat,\n });\n }\n }\n /**\n * Take a range of cells and return the format of the first cell containing a\n * number value. Returns a default format if the cell hasn't format. Returns\n * undefined if no number value in the range.\n */\n searchNumberFormat(sheetId, zones) {\n var _a;\n for (let zone of zones) {\n for (let row = zone.top; row <= zone.bottom; row++) {\n for (let col = zone.left; col <= zone.right; col++) {\n const cell = this.getters.getEvaluatedCell({ sheetId, col, row });\n if (cell.type === CellValueType.number &&\n !((_a = cell.format) === null || _a === void 0 ? void 0 : _a.match(DATETIME_FORMAT)) // reject dates\n ) {\n return cell.format || createDefaultFormat(cell.value);\n }\n }\n }\n }\n return undefined;\n }\n }\n\n class HeaderVisibilityUIPlugin extends UIPlugin {\n isRowHidden(sheetId, index) {\n return (this.getters.isRowHiddenByUser(sheetId, index) || this.getters.isRowFiltered(sheetId, index));\n }\n isColHidden(sheetId, index) {\n return this.getters.isColHiddenByUser(sheetId, index);\n }\n isHeaderHidden(sheetId, dimension, index) {\n return dimension === \"COL\"\n ? this.isColHidden(sheetId, index)\n : this.isRowHidden(sheetId, index);\n }\n getNextVisibleCellPosition({ sheetId, col, row }) {\n return {\n sheetId,\n col: this.findVisibleHeader(sheetId, \"COL\", range(col, this.getters.getNumberCols(sheetId))),\n row: this.findVisibleHeader(sheetId, \"ROW\", range(row, this.getters.getNumberRows(sheetId))),\n };\n }\n findVisibleHeader(sheetId, dimension, indexes) {\n return indexes.find((index) => this.getters.doesHeaderExist(sheetId, dimension, index) &&\n !this.isHeaderHidden(sheetId, dimension, index));\n }\n findLastVisibleColRowIndex(sheetId, dimension, { last, first }) {\n const lastVisibleIndex = range(last, first, -1).find((index) => !this.isHeaderHidden(sheetId, dimension, index));\n return lastVisibleIndex || first;\n }\n findFirstVisibleColRowIndex(sheetId, dimension) {\n const numberOfHeaders = this.getters.getNumberHeaders(sheetId, dimension);\n for (let i = 0; i < numberOfHeaders - 1; i++) {\n if (dimension === \"COL\" && !this.isColHidden(sheetId, i)) {\n return i;\n }\n if (dimension === \"ROW\" && !this.isRowHidden(sheetId, i)) {\n return i;\n }\n }\n return undefined;\n }\n exportForExcel(data) {\n for (const sheetData of data.sheets) {\n for (const [row, rowData] of Object.entries(sheetData.rows)) {\n const isHidden = this.isRowHidden(sheetData.id, Number(row));\n rowData.isHidden = isHidden;\n }\n }\n }\n }\n HeaderVisibilityUIPlugin.getters = [\n \"getNextVisibleCellPosition\",\n \"findVisibleHeader\",\n \"findLastVisibleColRowIndex\",\n \"findFirstVisibleColRowIndex\",\n \"isRowHidden\",\n \"isColHidden\",\n \"isHeaderHidden\",\n ];\n\n /**\n * HighlightPlugin\n */\n class HighlightPlugin extends UIPlugin {\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n getHighlights() {\n return this.prepareHighlights(this.getters.getComposerHighlights().concat(this.getters.getSelectionInputHighlights()));\n }\n // ---------------------------------------------------------------------------\n // Other\n // ---------------------------------------------------------------------------\n prepareHighlights(highlights) {\n return highlights\n .filter((x) => x.zone.top >= 0 &&\n x.zone.left >= 0 &&\n x.zone.bottom < this.getters.getNumberRows(x.sheetId) &&\n x.zone.right < this.getters.getNumberCols(x.sheetId))\n .map((highlight) => {\n const { height, width } = zoneToDimension(highlight.zone);\n const zone = height * width === 1\n ? this.getters.expandZone(highlight.sheetId, highlight.zone)\n : highlight.zone;\n return {\n ...highlight,\n zone,\n };\n });\n }\n // ---------------------------------------------------------------------------\n // Grid rendering\n // ---------------------------------------------------------------------------\n drawGrid(renderingContext) {\n // rendering selection highlights\n const { ctx, thinLineWidth } = renderingContext;\n const sheetId = this.getters.getActiveSheetId();\n const lineWidth = 3 * thinLineWidth;\n ctx.lineWidth = lineWidth;\n /**\n * We only need to draw the highlights of the current sheet.\n *\n * Note that there can be several times the same highlight in 'this.highlights'.\n * In order to avoid superposing the same color layer and modifying the final\n * opacity, we filter highlights to remove duplicates.\n */\n const highlights = this.getHighlights();\n for (let h of highlights.filter((highlight, index) => \n // For every highlight in the sheet, deduplicated by zone\n highlights.findIndex((h) => isEqual(h.zone, highlight.zone) && h.sheetId === sheetId) ===\n index)) {\n const { x, y, width, height } = this.getters.getVisibleRect(h.zone);\n if (width > 0 && height > 0) {\n ctx.strokeStyle = h.color;\n ctx.strokeRect(x + lineWidth / 2, y + lineWidth / 2, width - lineWidth, height - lineWidth);\n ctx.globalCompositeOperation = \"source-over\";\n ctx.fillStyle = h.color + \"20\";\n ctx.fillRect(x + lineWidth, y + lineWidth, width - 2 * lineWidth, height - 2 * lineWidth);\n }\n }\n }\n }\n HighlightPlugin.layers = [1 /* LAYERS.Highlights */];\n HighlightPlugin.getters = [\"getHighlights\"];\n\n class RendererPlugin extends UIPlugin {\n constructor() {\n super(...arguments);\n this.boxes = [];\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n /**\n * Returns the size, start and end coordinates of a column relative to the left\n * column of the current viewport\n */\n getColDimensionsInViewport(sheetId, col) {\n const left = Math.min(...this.getters.getSheetViewVisibleCols());\n const start = this.getters.getColRowOffsetInViewport(\"COL\", left, col);\n const size = this.getters.getColSize(sheetId, col);\n const isColHidden = this.getters.isColHidden(sheetId, col);\n return {\n start,\n size: size,\n end: start + (isColHidden ? 0 : size),\n };\n }\n /**\n * Returns the size, start and end coordinates of a row relative to the top row\n * of the current viewport\n */\n getRowDimensionsInViewport(sheetId, row) {\n const top = Math.min(...this.getters.getSheetViewVisibleRows());\n const start = this.getters.getColRowOffsetInViewport(\"ROW\", top, row);\n const size = this.getters.getRowSize(sheetId, row);\n const isRowHidden = this.getters.isRowHidden(sheetId, row);\n return {\n start,\n size: size,\n end: start + (isRowHidden ? 0 : size),\n };\n }\n /**\n * Get the offset of a header (see getColRowOffsetInViewport), adjusted with the header\n * size (HEADER_HEIGHT and HEADER_WIDTH)\n */\n getHeaderOffset(dimension, start, index) {\n let size = this.getters.getColRowOffsetInViewport(dimension, start, index);\n if (!this.getters.isDashboard()) {\n size += dimension === \"ROW\" ? HEADER_HEIGHT : HEADER_WIDTH;\n }\n return size;\n }\n // ---------------------------------------------------------------------------\n // Grid rendering\n // ---------------------------------------------------------------------------\n drawGrid(renderingContext, layer) {\n switch (layer) {\n case 0 /* LAYERS.Background */:\n this.boxes = this.getGridBoxes();\n this.drawBackground(renderingContext);\n this.drawOverflowingCellBackground(renderingContext);\n this.drawCellBackground(renderingContext);\n this.drawBorders(renderingContext);\n this.drawTexts(renderingContext);\n this.drawIcon(renderingContext);\n this.drawFrozenPanes(renderingContext);\n break;\n case 7 /* LAYERS.Headers */:\n if (!this.getters.isDashboard()) {\n this.drawHeaders(renderingContext);\n this.drawFrozenPanesHeaders(renderingContext);\n }\n break;\n }\n }\n drawBackground(renderingContext) {\n const { ctx, thinLineWidth } = renderingContext;\n const { width, height } = this.getters.getSheetViewDimensionWithHeaders();\n // white background\n ctx.fillStyle = \"#ffffff\";\n ctx.fillRect(0, 0, width + CANVAS_SHIFT, height + CANVAS_SHIFT);\n const areGridLinesVisible = !this.getters.isDashboard() &&\n this.getters.getGridLinesVisibility(this.getters.getActiveSheetId());\n const inset = areGridLinesVisible ? 0.1 * thinLineWidth : 0;\n if (areGridLinesVisible) {\n for (const box of this.boxes) {\n ctx.strokeStyle = CELL_BORDER_COLOR;\n ctx.lineWidth = thinLineWidth;\n ctx.strokeRect(box.x + inset, box.y + inset, box.width - 2 * inset, box.height - 2 * inset);\n }\n }\n }\n drawCellBackground(renderingContext) {\n const { ctx } = renderingContext;\n for (const box of this.boxes) {\n let style = box.style;\n if (style.fillColor && style.fillColor !== \"#ffffff\") {\n ctx.fillStyle = style.fillColor || \"#ffffff\";\n ctx.fillRect(box.x, box.y, box.width, box.height);\n }\n if (box.error) {\n ctx.fillStyle = \"red\";\n ctx.beginPath();\n ctx.moveTo(box.x + box.width - 5, box.y);\n ctx.lineTo(box.x + box.width, box.y);\n ctx.lineTo(box.x + box.width, box.y + 5);\n ctx.fill();\n }\n }\n }\n drawOverflowingCellBackground(renderingContext) {\n var _a, _b;\n const { ctx, thinLineWidth } = renderingContext;\n for (const box of this.boxes) {\n if (box.content && box.isOverflow) {\n const align = box.content.align || \"left\";\n let x;\n let width;\n const y = box.y + thinLineWidth / 2;\n const height = box.height - thinLineWidth;\n const clipWidth = Math.min(((_a = box.clipRect) === null || _a === void 0 ? void 0 : _a.width) || Infinity, box.content.width);\n if (align === \"left\") {\n x = box.x + thinLineWidth / 2;\n width = clipWidth - 2 * thinLineWidth;\n }\n else if (align === \"right\") {\n x = box.x + box.width - thinLineWidth / 2;\n width = -clipWidth + 2 * thinLineWidth;\n }\n else {\n x =\n (((_b = box.clipRect) === null || _b === void 0 ? void 0 : _b.x) || box.x + box.width / 2 - box.content.width / 2) + thinLineWidth / 2;\n width = clipWidth - 2 * thinLineWidth;\n }\n ctx.fillStyle = \"#ffffff\";\n ctx.fillRect(x, y, width, height);\n }\n }\n }\n drawBorders(renderingContext) {\n const { ctx, thinLineWidth } = renderingContext;\n for (let box of this.boxes) {\n const border = box.border;\n if (border) {\n const { x, y, width, height } = box;\n if (border.left) {\n drawBorder(border.left, x, y, x, y + height);\n }\n if (border.top) {\n drawBorder(border.top, x, y, x + width, y);\n }\n if (border.right) {\n drawBorder(border.right, x + width, y, x + width, y + height);\n }\n if (border.bottom) {\n drawBorder(border.bottom, x, y + height, x + width, y + height);\n }\n }\n }\n function drawBorder([style, color], x1, y1, x2, y2) {\n ctx.strokeStyle = color;\n ctx.lineWidth = (style === \"thin\" ? 2 : 3) * thinLineWidth;\n ctx.beginPath();\n ctx.moveTo(x1, y1);\n ctx.lineTo(x2, y2);\n ctx.stroke();\n }\n }\n drawTexts(renderingContext) {\n const { ctx, thinLineWidth } = renderingContext;\n ctx.textBaseline = \"top\";\n let currentFont;\n for (let box of this.boxes) {\n if (box.content) {\n const style = box.style || {};\n const align = box.content.align || \"left\";\n // compute font and textColor\n const font = computeTextFont(style);\n if (font !== currentFont) {\n currentFont = font;\n ctx.font = font;\n }\n ctx.fillStyle = style.textColor || \"#000\";\n // compute horizontal align start point parameter\n let x = box.x;\n if (align === \"left\") {\n x += MIN_CELL_TEXT_MARGIN + (box.image ? box.image.size + MIN_CF_ICON_MARGIN : 0);\n }\n else if (align === \"right\") {\n x +=\n box.width -\n MIN_CELL_TEXT_MARGIN -\n (box.isFilterHeader ? ICON_EDGE_LENGTH + FILTER_ICON_MARGIN : 0);\n }\n else {\n x += box.width / 2;\n }\n // horizontal align text direction\n ctx.textAlign = align;\n // clip rect if needed\n if (box.clipRect) {\n ctx.save();\n ctx.beginPath();\n const { x, y, width, height } = box.clipRect;\n ctx.rect(x, y, width, height);\n ctx.clip();\n }\n // compute vertical align start point parameter:\n const textLineHeight = computeTextFontSizeInPixels(style);\n const numberOfLines = box.content.textLines.length;\n let y = this.computeTextYCoordinate(box, textLineHeight, numberOfLines);\n // use the horizontal and the vertical start points to:\n // fill text / fill strikethrough / fill underline\n for (let brokenLine of box.content.textLines) {\n ctx.fillText(brokenLine, Math.round(x), Math.round(y));\n if (style.strikethrough || style.underline) {\n const lineWidth = computeTextWidth(ctx, brokenLine, style);\n let _x = x;\n if (align === \"right\") {\n _x -= lineWidth;\n }\n else if (align === \"center\") {\n _x -= lineWidth / 2;\n }\n if (style.strikethrough) {\n ctx.fillRect(_x, y + textLineHeight / 2, lineWidth, 2.6 * thinLineWidth);\n }\n if (style.underline) {\n ctx.fillRect(_x, y + textLineHeight + 1, lineWidth, 1.3 * thinLineWidth);\n }\n }\n y += MIN_CELL_TEXT_MARGIN + textLineHeight;\n }\n if (box.clipRect) {\n ctx.restore();\n }\n }\n }\n }\n drawIcon(renderingContext) {\n const { ctx } = renderingContext;\n for (const box of this.boxes) {\n if (box.image) {\n const icon = box.image.image;\n if (box.image.clipIcon) {\n ctx.save();\n ctx.beginPath();\n const { x, y, width, height } = box.image.clipIcon;\n ctx.rect(x, y, width, height);\n ctx.clip();\n }\n const iconSize = box.image.size;\n const y = this.computeTextYCoordinate(box, iconSize);\n ctx.drawImage(icon, box.x + MIN_CF_ICON_MARGIN, y, iconSize, iconSize);\n if (box.image.clipIcon) {\n ctx.restore();\n }\n }\n }\n }\n /** Compute the vertical start point from which a text line should be draw.\n *\n * Note that in case the cell does not have enough spaces to display its text lines,\n * (wrapping cell case) then the vertical align should be at the top.\n * */\n computeTextYCoordinate(box, textLineHeight, numberOfLines = 1) {\n const y = box.y + 1;\n const textHeight = computeTextLinesHeight(textLineHeight, numberOfLines);\n const hasEnoughSpaces = box.height > textHeight + MIN_CELL_TEXT_MARGIN * 2;\n const verticalAlign = box.verticalAlign || \"middle\";\n if (hasEnoughSpaces) {\n if (verticalAlign === \"middle\") {\n return y + (box.height - textHeight) / 2;\n }\n if (verticalAlign === \"bottom\") {\n return y + box.height - textHeight - MIN_CELL_TEXT_MARGIN;\n }\n }\n return y + MIN_CELL_TEXT_MARGIN;\n }\n drawHeaders(renderingContext) {\n const { ctx, thinLineWidth } = renderingContext;\n const visibleCols = this.getters.getSheetViewVisibleCols();\n const left = visibleCols[0];\n const right = visibleCols[visibleCols.length - 1];\n const visibleRows = this.getters.getSheetViewVisibleRows();\n const top = visibleRows[0];\n const bottom = visibleRows[visibleRows.length - 1];\n const { width, height } = this.getters.getSheetViewDimensionWithHeaders();\n const selection = this.getters.getSelectedZones();\n const selectedCols = getZonesCols(selection);\n const selectedRows = getZonesRows(selection);\n const sheetId = this.getters.getActiveSheetId();\n const numberOfCols = this.getters.getNumberCols(sheetId);\n const numberOfRows = this.getters.getNumberRows(sheetId);\n const activeCols = this.getters.getActiveCols();\n const activeRows = this.getters.getActiveRows();\n ctx.font = `400 ${HEADER_FONT_SIZE}px ${DEFAULT_FONT}`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.lineWidth = thinLineWidth;\n ctx.strokeStyle = \"#333\";\n // Columns headers background\n for (let col = left; col <= right; col++) {\n const colZone = { left: col, right: col, top: 0, bottom: numberOfRows - 1 };\n const { x, width } = this.getters.getVisibleRect(colZone);\n const colHasFilter = this.getters.doesZonesContainFilter(sheetId, [colZone]);\n const isColActive = activeCols.has(col);\n const isColSelected = selectedCols.has(col);\n if (isColActive) {\n ctx.fillStyle = colHasFilter ? FILTERS_COLOR : BACKGROUND_HEADER_ACTIVE_COLOR;\n }\n else if (isColSelected) {\n ctx.fillStyle = colHasFilter\n ? BACKGROUND_HEADER_SELECTED_FILTER_COLOR\n : BACKGROUND_HEADER_SELECTED_COLOR;\n }\n else {\n ctx.fillStyle = colHasFilter ? BACKGROUND_HEADER_FILTER_COLOR : BACKGROUND_HEADER_COLOR;\n }\n ctx.fillRect(x, 0, width, HEADER_HEIGHT);\n }\n // Rows headers background\n for (let row = top; row <= bottom; row++) {\n const rowZone = { top: row, bottom: row, left: 0, right: numberOfCols - 1 };\n const { y, height } = this.getters.getVisibleRect(rowZone);\n const rowHasFilter = this.getters.doesZonesContainFilter(sheetId, [rowZone]);\n const isRowActive = activeRows.has(row);\n const isRowSelected = selectedRows.has(row);\n if (isRowActive) {\n ctx.fillStyle = rowHasFilter ? FILTERS_COLOR : BACKGROUND_HEADER_ACTIVE_COLOR;\n }\n else if (isRowSelected) {\n ctx.fillStyle = rowHasFilter\n ? BACKGROUND_HEADER_SELECTED_FILTER_COLOR\n : BACKGROUND_HEADER_SELECTED_COLOR;\n }\n else {\n ctx.fillStyle = rowHasFilter ? BACKGROUND_HEADER_FILTER_COLOR : BACKGROUND_HEADER_COLOR;\n }\n ctx.fillRect(0, y, HEADER_WIDTH, height);\n }\n // 2 main lines\n ctx.beginPath();\n ctx.moveTo(HEADER_WIDTH, 0);\n ctx.lineTo(HEADER_WIDTH, height);\n ctx.moveTo(0, HEADER_HEIGHT);\n ctx.lineTo(width, HEADER_HEIGHT);\n ctx.strokeStyle = HEADER_BORDER_COLOR;\n ctx.stroke();\n ctx.beginPath();\n // column text + separator\n for (const i of visibleCols) {\n const colSize = this.getters.getColSize(sheetId, i);\n const colName = numberToLetters(i);\n ctx.fillStyle = activeCols.has(i) ? \"#fff\" : TEXT_HEADER_COLOR;\n let colStart = this.getHeaderOffset(\"COL\", left, i);\n ctx.fillText(colName, colStart + colSize / 2, HEADER_HEIGHT / 2);\n ctx.moveTo(colStart + colSize, 0);\n ctx.lineTo(colStart + colSize, HEADER_HEIGHT);\n }\n // row text + separator\n for (const i of visibleRows) {\n const rowSize = this.getters.getRowSize(sheetId, i);\n ctx.fillStyle = activeRows.has(i) ? \"#fff\" : TEXT_HEADER_COLOR;\n let rowStart = this.getHeaderOffset(\"ROW\", top, i);\n ctx.fillText(String(i + 1), HEADER_WIDTH / 2, rowStart + rowSize / 2);\n ctx.moveTo(0, rowStart + rowSize);\n ctx.lineTo(HEADER_WIDTH, rowStart + rowSize);\n }\n ctx.stroke();\n }\n drawFrozenPanesHeaders(renderingContext) {\n const { ctx, thinLineWidth } = renderingContext;\n const { x: offsetCorrectionX, y: offsetCorrectionY } = this.getters.getMainViewportCoordinates();\n const widthCorrection = this.getters.isDashboard() ? 0 : HEADER_WIDTH;\n const heightCorrection = this.getters.isDashboard() ? 0 : HEADER_HEIGHT;\n ctx.lineWidth = 6 * thinLineWidth;\n ctx.strokeStyle = \"#BCBCBC\";\n ctx.beginPath();\n if (offsetCorrectionX) {\n ctx.moveTo(widthCorrection + offsetCorrectionX, 0);\n ctx.lineTo(widthCorrection + offsetCorrectionX, heightCorrection);\n }\n if (offsetCorrectionY) {\n ctx.moveTo(0, heightCorrection + offsetCorrectionY);\n ctx.lineTo(widthCorrection, heightCorrection + offsetCorrectionY);\n }\n ctx.stroke();\n }\n drawFrozenPanes(renderingContext) {\n const { ctx, thinLineWidth } = renderingContext;\n const { x: offsetCorrectionX, y: offsetCorrectionY } = this.getters.getMainViewportCoordinates();\n const visibleCols = this.getters.getSheetViewVisibleCols();\n const left = visibleCols[0];\n const right = visibleCols[visibleCols.length - 1];\n const visibleRows = this.getters.getSheetViewVisibleRows();\n const top = visibleRows[0];\n const bottom = visibleRows[visibleRows.length - 1];\n const viewport = { left, right, top, bottom };\n const rect = this.getters.getVisibleRect(viewport);\n const widthCorrection = this.getters.isDashboard() ? 0 : HEADER_WIDTH;\n const heightCorrection = this.getters.isDashboard() ? 0 : HEADER_HEIGHT;\n ctx.lineWidth = 6 * thinLineWidth;\n ctx.strokeStyle = \"#DADFE8\";\n ctx.beginPath();\n if (offsetCorrectionX) {\n ctx.moveTo(widthCorrection + offsetCorrectionX, heightCorrection);\n ctx.lineTo(widthCorrection + offsetCorrectionX, rect.height + heightCorrection);\n }\n if (offsetCorrectionY) {\n ctx.moveTo(widthCorrection, heightCorrection + offsetCorrectionY);\n ctx.lineTo(rect.width + widthCorrection, heightCorrection + offsetCorrectionY);\n }\n ctx.stroke();\n }\n findNextEmptyCol(base, max, row) {\n const sheetId = this.getters.getActiveSheetId();\n let col = base;\n while (col < max) {\n const position = { sheetId, col: col + 1, row };\n const nextCell = this.getters.getEvaluatedCell(position);\n const nextCellBorder = this.getters.getCellBorderWithFilterBorder(position);\n if (nextCell.type !== CellValueType.empty ||\n this.getters.isInMerge(position) ||\n (nextCellBorder === null || nextCellBorder === void 0 ? void 0 : nextCellBorder.left)) {\n return col;\n }\n col++;\n }\n return col;\n }\n findPreviousEmptyCol(base, min, row) {\n const sheetId = this.getters.getActiveSheetId();\n let col = base;\n while (col > min) {\n const position = { sheetId, col: col - 1, row };\n const previousCell = this.getters.getEvaluatedCell(position);\n const previousCellBorder = this.getters.getCellBorderWithFilterBorder(position);\n if (previousCell.type !== CellValueType.empty ||\n this.getters.isInMerge(position) ||\n (previousCellBorder === null || previousCellBorder === void 0 ? void 0 : previousCellBorder.right)) {\n return col;\n }\n col--;\n }\n return col;\n }\n computeCellAlignment(position, isOverflowing) {\n const cell = this.getters.getCell(position);\n if ((cell === null || cell === void 0 ? void 0 : cell.isFormula) && this.getters.shouldShowFormulas()) {\n return \"left\";\n }\n const { align } = this.getters.getCellStyle(position);\n const evaluatedCell = this.getters.getEvaluatedCell(position);\n if (isOverflowing && evaluatedCell.type === CellValueType.number) {\n return align !== \"center\" ? \"left\" : align;\n }\n return align || evaluatedCell.defaultAlign;\n }\n createZoneBox(sheetId, zone, viewport) {\n const { left, right } = viewport;\n const col = zone.left;\n const row = zone.top;\n const position = { sheetId, col, row };\n const cell = this.getters.getEvaluatedCell(position);\n const showFormula = this.getters.shouldShowFormulas();\n const { x, y, width, height } = this.getters.getVisibleRect(zone);\n const { verticalAlign } = this.getters.getCellStyle(position);\n const box = {\n x,\n y,\n width,\n height,\n border: this.getters.getCellBorderWithFilterBorder(position) || undefined,\n style: this.getters.getCellComputedStyle(position),\n verticalAlign,\n };\n if (cell.type === CellValueType.empty) {\n return box;\n }\n /** Icon CF */\n const cfIcon = this.getters.getConditionalIcon(position);\n const fontSizePX = computeTextFontSizeInPixels(box.style);\n const iconBoxWidth = cfIcon ? MIN_CF_ICON_MARGIN + fontSizePX : 0;\n if (cfIcon) {\n box.image = {\n type: \"icon\",\n size: fontSizePX,\n clipIcon: { x: box.x, y: box.y, width: Math.min(iconBoxWidth, width), height },\n image: ICONS[cfIcon].img,\n };\n }\n /** Filter Header */\n box.isFilterHeader = this.getters.isFilterHeader(position);\n const headerIconWidth = box.isFilterHeader ? FILTER_ICON_EDGE_LENGTH + FILTER_ICON_MARGIN : 0;\n /** Content */\n const text = this.getters.getCellText(position, showFormula);\n const textWidth = this.getters.getTextWidth(position) + MIN_CELL_TEXT_MARGIN;\n const wrapping = this.getters.getCellStyle(position).wrapping || \"overflow\";\n const multiLineText = wrapping === \"wrap\"\n ? this.getters.getCellMultiLineText(position, width - 2 * MIN_CELL_TEXT_MARGIN)\n : [text];\n const contentWidth = iconBoxWidth + textWidth + headerIconWidth;\n const align = this.computeCellAlignment(position, contentWidth > width);\n box.content = {\n textLines: multiLineText,\n width: wrapping === \"overflow\" ? textWidth : width,\n align,\n };\n /** Error */\n if (cell.type === CellValueType.error && cell.error.logLevel > CellErrorLevel.silent) {\n box.error = cell.error.message;\n }\n /** ClipRect */\n const isOverflowing = contentWidth > width || fontSizePX > height;\n if (cfIcon || box.isFilterHeader) {\n box.clipRect = {\n x: box.x + iconBoxWidth,\n y: box.y,\n width: Math.max(0, width - iconBoxWidth - headerIconWidth),\n height,\n };\n }\n else if (isOverflowing && wrapping === \"overflow\") {\n let nextColIndex, previousColIndex;\n const isCellInMerge = this.getters.isInMerge(position);\n if (isCellInMerge) {\n // Always clip merges\n nextColIndex = this.getters.getMerge(position).right;\n previousColIndex = col;\n }\n else {\n nextColIndex = this.findNextEmptyCol(col, right, row);\n previousColIndex = this.findPreviousEmptyCol(col, left, row);\n box.isOverflow = true;\n }\n switch (align) {\n case \"left\": {\n const emptyZoneOnTheLeft = positionToZone({ col: nextColIndex, row });\n const { x, y, width, height } = this.getters.getVisibleRect(union(zone, emptyZoneOnTheLeft));\n if (width < contentWidth || fontSizePX > height) {\n box.clipRect = { x, y, width, height };\n }\n break;\n }\n case \"right\": {\n const emptyZoneOnTheRight = positionToZone({ col: previousColIndex, row });\n const { x, y, width, height } = this.getters.getVisibleRect(union(zone, emptyZoneOnTheRight));\n if (width < contentWidth || fontSizePX > height) {\n box.clipRect = { x, y, width, height };\n }\n break;\n }\n case \"center\": {\n const emptyZone = {\n ...zone,\n left: previousColIndex,\n right: nextColIndex,\n };\n const { x, y, height, width } = this.getters.getVisibleRect(emptyZone);\n const halfContentWidth = contentWidth / 2;\n const boxMiddle = box.x + box.width / 2;\n if (x + width < boxMiddle + halfContentWidth ||\n x > boxMiddle - halfContentWidth ||\n fontSizePX > height) {\n const clipX = x > boxMiddle - halfContentWidth ? x : boxMiddle - halfContentWidth;\n const clipWidth = x + width - clipX;\n box.clipRect = { x: clipX, y, width: clipWidth, height };\n }\n break;\n }\n }\n }\n else if (wrapping === \"clip\" || wrapping === \"wrap\") {\n box.clipRect = {\n x: box.x,\n y: box.y,\n width,\n height,\n };\n }\n return box;\n }\n getGridBoxes() {\n const boxes = [];\n const visibleCols = this.getters.getSheetViewVisibleCols();\n const left = visibleCols[0];\n const right = visibleCols[visibleCols.length - 1];\n const visibleRows = this.getters.getSheetViewVisibleRows();\n const top = visibleRows[0];\n const bottom = visibleRows[visibleRows.length - 1];\n const viewport = { left, right, top, bottom };\n const sheetId = this.getters.getActiveSheetId();\n for (const row of visibleRows) {\n for (const col of visibleCols) {\n const position = { sheetId, col, row };\n if (this.getters.isInMerge(position)) {\n continue;\n }\n boxes.push(this.createZoneBox(sheetId, positionToZone(position), viewport));\n }\n }\n for (const merge of this.getters.getMerges(sheetId)) {\n if (this.getters.isMergeHidden(sheetId, merge)) {\n continue;\n }\n if (overlap(merge, viewport)) {\n const box = this.createZoneBox(sheetId, merge, viewport);\n const borderBottomRight = this.getters.getCellBorder({\n sheetId,\n col: merge.right,\n row: merge.bottom,\n });\n box.border = {\n ...box.border,\n bottom: borderBottomRight ? borderBottomRight.bottom : undefined,\n right: borderBottomRight ? borderBottomRight.right : undefined,\n };\n box.isMerge = true;\n boxes.push(box);\n }\n }\n return boxes;\n }\n }\n RendererPlugin.layers = [0 /* LAYERS.Background */, 7 /* LAYERS.Headers */];\n RendererPlugin.getters = [\"getColDimensionsInViewport\", \"getRowDimensionsInViewport\"];\n\n /**\n * Selection input Plugin\n *\n * The SelectionInput component input and output are both arrays of strings, but\n * it requires an intermediary internal state to work.\n * This plugin handles this internal state.\n */\n class SelectionInputPlugin extends UIPlugin {\n constructor(config, initialRanges, inputHasSingleRange) {\n super(config);\n this.inputHasSingleRange = inputHasSingleRange;\n this.ranges = [];\n this.focusedRangeIndex = null;\n this.willAddNewRange = false;\n this.insertNewRange(0, initialRanges);\n this.activeSheet = this.getters.getActiveSheetId();\n if (this.ranges.length === 0) {\n this.insertNewRange(this.ranges.length, [\"\"]);\n this.focusLast();\n }\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"ADD_EMPTY_RANGE\":\n if (this.inputHasSingleRange && this.ranges.length === 1) {\n return 30 /* CommandResult.MaximumRangesReached */;\n }\n break;\n }\n return 0 /* CommandResult.Success */;\n }\n handleEvent(event) {\n const xc = zoneToXc(event.anchor.zone);\n const inputSheetId = this.activeSheet;\n const sheetId = this.getters.getActiveSheetId();\n const sheetName = this.getters.getSheetName(sheetId);\n this.add([sheetId === inputSheetId ? xc : `${getComposerSheetName(sheetName)}!${xc}`]);\n }\n handle(cmd) {\n switch (cmd.type) {\n case \"UNFOCUS_SELECTION_INPUT\":\n this.unfocus();\n break;\n case \"FOCUS_RANGE\":\n this.focus(this.getIndex(cmd.rangeId));\n break;\n case \"CHANGE_RANGE\": {\n const index = this.getIndex(cmd.rangeId);\n if (index !== null && this.focusedRangeIndex !== index) {\n this.focus(index);\n }\n if (index !== null) {\n const values = cmd.value.split(\",\").map((reference) => reference.trim());\n this.setRange(index, values);\n }\n break;\n }\n case \"ADD_EMPTY_RANGE\":\n this.insertNewRange(this.ranges.length, [\"\"]);\n this.focusLast();\n break;\n case \"REMOVE_RANGE\":\n const index = this.getIndex(cmd.rangeId);\n if (index !== null) {\n this.removeRange(index);\n }\n break;\n case \"STOP_SELECTION_INPUT\":\n this.willAddNewRange = false;\n break;\n case \"PREPARE_SELECTION_INPUT_EXPANSION\": {\n const index = this.focusedRangeIndex;\n if (index !== null && !this.inputHasSingleRange) {\n this.willAddNewRange = this.ranges[index].xc.trim() !== \"\";\n }\n break;\n }\n case \"ACTIVATE_SHEET\": {\n if (cmd.sheetIdFrom !== cmd.sheetIdTo) {\n const { col, row } = this.getters.getNextVisibleCellPosition({\n sheetId: cmd.sheetIdTo,\n col: 0,\n row: 0,\n });\n const zone = this.getters.expandZone(cmd.sheetIdTo, positionToZone({ col, row }));\n this.selection.resetAnchor(this, { cell: { col, row }, zone });\n }\n }\n }\n }\n unsubscribe() {\n this.unfocus();\n }\n // ---------------------------------------------------------------------------\n // Getters || only callable by the parent\n // ---------------------------------------------------------------------------\n getSelectionInputValue() {\n return this.cleanInputs(this.ranges.map((range) => {\n return range.xc ? range.xc : \"\";\n }));\n }\n getSelectionInputHighlights() {\n return this.ranges.map((input) => this.inputToHighlights(input)).flat();\n }\n // ---------------------------------------------------------------------------\n // Other\n // ---------------------------------------------------------------------------\n /**\n * Focus a given range or remove the focus.\n */\n focus(index) {\n this.focusedRangeIndex = index;\n }\n focusLast() {\n this.focus(this.ranges.length - 1);\n }\n unfocus() {\n this.focusedRangeIndex = null;\n }\n add(newRanges) {\n if (this.focusedRangeIndex === null || newRanges.length === 0) {\n return;\n }\n if (this.willAddNewRange) {\n this.insertNewRange(this.ranges.length, newRanges);\n this.focusLast();\n this.willAddNewRange = false;\n }\n else {\n this.setRange(this.focusedRangeIndex, newRanges);\n }\n }\n setContent(index, xc) {\n this.ranges[index] = {\n ...this.ranges[index],\n xc,\n };\n }\n /**\n * Insert new inputs after the given index.\n */\n insertNewRange(index, values) {\n this.ranges.splice(index, 0, ...values.map((xc, i) => ({\n xc,\n id: (this.ranges.length + i + 1).toString(),\n color: colors$1[(this.ranges.length + i) % colors$1.length],\n })));\n }\n /**\n * Set a new value in a given range input. If more than one value is provided,\n * new inputs will be added.\n */\n setRange(index, values) {\n const [, ...additionalValues] = values;\n this.setContent(index, values[0]);\n this.insertNewRange(index + 1, additionalValues);\n // focus the last newly added range\n if (additionalValues.length) {\n this.focus(index + additionalValues.length);\n }\n }\n removeRange(index) {\n this.ranges.splice(index, 1);\n if (this.focusedRangeIndex !== null) {\n this.focusLast();\n }\n }\n /**\n * Convert highlights input format to the command format.\n * The first xc in the input range will keep its color.\n * Invalid ranges and ranges from other sheets than the active sheets\n * are ignored.\n */\n inputToHighlights({ xc, color }) {\n const XCs = this.cleanInputs([xc])\n .filter((range) => this.getters.isRangeValid(range))\n .filter((reference) => this.shouldBeHighlighted(this.activeSheet, reference));\n return XCs.map((xc) => {\n const { sheetName } = splitReference(xc);\n return {\n zone: this.getters.getRangeFromSheetXC(this.activeSheet, xc).zone,\n sheetId: (sheetName && this.getters.getSheetIdByName(sheetName)) || this.activeSheet,\n color,\n };\n });\n }\n cleanInputs(ranges) {\n return ranges\n .map((xc) => xc.split(\",\"))\n .flat()\n .map((xc) => xc.trim())\n .filter((xc) => xc !== \"\");\n }\n /**\n * Check if a cell or range reference should be highlighted.\n * It should be highlighted if it references the current active sheet.\n * Note that if no sheet name is given in the reference (\"A1\"), it refers to the\n * active sheet when the selection input was enabled which might be different from\n * the current active sheet.\n */\n shouldBeHighlighted(inputSheetId, reference) {\n const { sheetName } = splitReference(reference);\n const sheetId = this.getters.getSheetIdByName(sheetName);\n const activeSheetId = this.getters.getActiveSheet().id;\n const valid = this.getters.isRangeValid(reference);\n return (valid &&\n (sheetId === activeSheetId || (sheetId === undefined && activeSheetId === inputSheetId)));\n }\n /**\n * Return the index of a range given its id\n * or `null` if the range is not found.\n */\n getIndex(rangeId) {\n const index = this.ranges.findIndex((range) => range.id === rangeId);\n return index >= 0 ? index : null;\n }\n }\n SelectionInputPlugin.layers = [1 /* LAYERS.Highlights */];\n SelectionInputPlugin.getters = [];\n\n /**\n * Selection input Plugin\n *\n * The SelectionInput component input and output are both arrays of strings, but\n * it requires an intermediary internal state to work.\n * This plugin handles this internal state.\n */\n class SelectionInputsManagerPlugin extends UIPlugin {\n constructor(config) {\n super(config);\n this.config = config;\n this.inputs = {};\n this.focusedInputId = null;\n }\n get currentInput() {\n return this.focusedInputId ? this.inputs[this.focusedInputId] : null;\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n allowDispatch(cmd) {\n var _a, _b;\n switch (cmd.type) {\n case \"FOCUS_RANGE\":\n const index = (_a = this.currentInput) === null || _a === void 0 ? void 0 : _a.getIndex(cmd.rangeId);\n if (this.focusedInputId === cmd.id && ((_b = this.currentInput) === null || _b === void 0 ? void 0 : _b.focusedRangeIndex) === index) {\n return 29 /* CommandResult.InputAlreadyFocused */;\n }\n break;\n }\n if (this.currentInput) {\n return this.currentInput.allowDispatch(cmd);\n }\n return 0 /* CommandResult.Success */;\n }\n handle(cmd) {\n var _a;\n switch (cmd.type) {\n case \"ENABLE_NEW_SELECTION_INPUT\":\n this.initInput(cmd.id, cmd.initialRanges || [], cmd.hasSingleRange);\n break;\n case \"DISABLE_SELECTION_INPUT\":\n if (this.focusedInputId === cmd.id) {\n this.unfocus();\n }\n delete this.inputs[cmd.id];\n break;\n case \"UNFOCUS_SELECTION_INPUT\":\n this.unfocus();\n break;\n case \"ADD_EMPTY_RANGE\":\n case \"REMOVE_RANGE\":\n if (cmd.id !== this.focusedInputId) {\n const input = this.inputs[cmd.id];\n this.selection.capture(input, { cell: { col: 0, row: 0 }, zone: positionToZone({ col: 0, row: 0 }) }, { handleEvent: input.handleEvent.bind(input) });\n this.focusedInputId = cmd.id;\n }\n break;\n case \"FOCUS_RANGE\":\n case \"CHANGE_RANGE\":\n if (cmd.id !== this.focusedInputId) {\n const input = this.inputs[cmd.id];\n const range = input.ranges.find((range) => range.id === cmd.rangeId);\n const sheetId = this.getters.getActiveSheetId();\n const zone = this.getters.getRangeFromSheetXC(sheetId, (range === null || range === void 0 ? void 0 : range.xc) || \"A1\").zone;\n this.selection.capture(input, { cell: { col: zone.left, row: zone.top }, zone }, { handleEvent: input.handleEvent.bind(input) });\n this.focusedInputId = cmd.id;\n }\n break;\n }\n (_a = this.currentInput) === null || _a === void 0 ? void 0 : _a.handle(cmd);\n }\n unsubscribe() {\n this.unfocus();\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n /**\n * Return a list of all valid XCs.\n * e.g. [\"A1\", \"Sheet2!B3\", \"E12\"]\n */\n getSelectionInput(id) {\n if (!this.inputs[id]) {\n return [];\n }\n return this.inputs[id].ranges.map((input, index) => Object.assign({}, input, {\n color: this.focusedInputId === id &&\n this.inputs[id].focusedRangeIndex !== null &&\n this.isRangeValid(input.xc)\n ? input.color\n : null,\n isFocused: this.focusedInputId === id && this.inputs[id].focusedRangeIndex === index,\n }));\n }\n isRangeValid(reference) {\n if (!reference) {\n return false;\n }\n const { xc, sheetName } = splitReference(reference);\n return (xc.match(rangeReference) !== null &&\n (!sheetName || this.getters.getSheetIdByName(sheetName) !== undefined));\n }\n getSelectionInputValue(id) {\n return this.inputs[id].getSelectionInputValue();\n }\n getSelectionInputHighlights() {\n if (!this.focusedInputId) {\n return [];\n }\n return this.inputs[this.focusedInputId].getSelectionInputHighlights();\n }\n // ---------------------------------------------------------------------------\n // Other\n // ---------------------------------------------------------------------------\n initInput(id, initialRanges, inputHasSingleRange = false) {\n this.inputs[id] = new SelectionInputPlugin(this.config, initialRanges, inputHasSingleRange);\n if (initialRanges.length === 0) {\n const input = this.inputs[id];\n const anchor = {\n zone: positionToZone({ col: 0, row: 0 }),\n cell: { col: 0, row: 0 },\n };\n this.selection.capture(input, anchor, { handleEvent: input.handleEvent.bind(input) });\n this.focusedInputId = id;\n }\n }\n unfocus() {\n this.selection.release(this.currentInput);\n this.focusedInputId = null;\n }\n }\n SelectionInputsManagerPlugin.layers = [1 /* LAYERS.Highlights */];\n SelectionInputsManagerPlugin.getters = [\n \"getSelectionInput\",\n \"getSelectionInputValue\",\n \"isRangeValid\",\n \"getSelectionInputHighlights\",\n ];\n\n /**\n * This is a generic event bus based on the Owl event bus.\n * This bus however ensures type safety across events and subscription callbacks.\n */\n class EventBus {\n constructor() {\n this.subscriptions = {};\n }\n /**\n * Add a listener for the 'eventType' events.\n *\n * Note that the 'owner' of this event can be anything, but will more likely\n * be a component or a class. The idea is that the callback will be called with\n * the proper owner bound.\n *\n * Also, the owner should be kind of unique. This will be used to remove the\n * listener.\n */\n on(type, owner, callback) {\n if (!callback) {\n throw new Error(\"Missing callback\");\n }\n if (!this.subscriptions[type]) {\n this.subscriptions[type] = [];\n }\n this.subscriptions[type].push({\n owner,\n callback,\n });\n }\n /**\n * Emit an event of type 'eventType'. Any extra arguments will be passed to\n * the listeners callback.\n */\n trigger(type, payload) {\n const subs = this.subscriptions[type] || [];\n for (let i = 0, iLen = subs.length; i < iLen; i++) {\n const sub = subs[i];\n sub.callback.call(sub.owner, payload);\n }\n }\n /**\n * Remove a listener\n */\n off(eventType, owner) {\n const subs = this.subscriptions[eventType];\n if (subs) {\n this.subscriptions[eventType] = subs.filter((s) => s.owner !== owner);\n }\n }\n /**\n * Remove all subscriptions.\n */\n clear() {\n this.subscriptions = {};\n }\n }\n\n /*\n * This file contains the specifics transformations\n */\n otRegistry.addTransformation(\"ADD_COLUMNS_ROWS\", [\"CREATE_CHART\", \"UPDATE_CHART\"], updateChartRangesTransformation);\n otRegistry.addTransformation(\"REMOVE_COLUMNS_ROWS\", [\"CREATE_CHART\", \"UPDATE_CHART\"], updateChartRangesTransformation);\n otRegistry.addTransformation(\"DELETE_SHEET\", [\"MOVE_RANGES\"], transformTargetSheetId);\n otRegistry.addTransformation(\"DELETE_FIGURE\", [\"UPDATE_FIGURE\", \"UPDATE_CHART\"], updateChartFigure);\n otRegistry.addTransformation(\"CREATE_SHEET\", [\"CREATE_SHEET\"], createSheetTransformation);\n otRegistry.addTransformation(\"ADD_MERGE\", [\"ADD_MERGE\", \"REMOVE_MERGE\", \"CREATE_FILTER_TABLE\"], mergeTransformation);\n otRegistry.addTransformation(\"ADD_COLUMNS_ROWS\", [\"FREEZE_COLUMNS\", \"FREEZE_ROWS\"], freezeTransformation);\n otRegistry.addTransformation(\"REMOVE_COLUMNS_ROWS\", [\"FREEZE_COLUMNS\", \"FREEZE_ROWS\"], freezeTransformation);\n otRegistry.addTransformation(\"CREATE_FILTER_TABLE\", [\"CREATE_FILTER_TABLE\", \"ADD_MERGE\"], createTableTransformation);\n function transformTargetSheetId(cmd, executed) {\n const deletedSheetId = executed.sheetId;\n if (cmd.targetSheetId === deletedSheetId || cmd.sheetId === deletedSheetId) {\n return undefined;\n }\n return cmd;\n }\n function updateChartFigure(toTransform, executed) {\n if (toTransform.id === executed.id) {\n return undefined;\n }\n return toTransform;\n }\n function updateChartRangesTransformation(toTransform, executed) {\n return {\n ...toTransform,\n definition: transformDefinition(toTransform.definition, executed),\n };\n }\n function createSheetTransformation(cmd, executed) {\n var _a;\n if (cmd.name === executed.name) {\n return {\n ...cmd,\n name: ((_a = cmd.name) === null || _a === void 0 ? void 0 : _a.match(/\\d+/))\n ? cmd.name.replace(/\\d+/, (n) => (parseInt(n) + 1).toString())\n : `${cmd.name}~`,\n position: cmd.position + 1,\n };\n }\n return cmd;\n }\n function mergeTransformation(cmd, executed) {\n if (cmd.sheetId !== executed.sheetId) {\n return cmd;\n }\n const target = [];\n for (const zone1 of cmd.target) {\n for (const zone2 of executed.target) {\n if (!overlap(zone1, zone2)) {\n target.push({ ...zone1 });\n }\n }\n }\n if (target.length) {\n return { ...cmd, target };\n }\n return undefined;\n }\n function freezeTransformation(cmd, executed) {\n if (cmd.sheetId !== executed.sheetId) {\n return cmd;\n }\n const dimension = cmd.type === \"FREEZE_COLUMNS\" ? \"COL\" : \"ROW\";\n if (dimension !== executed.dimension) {\n return cmd;\n }\n let quantity = cmd[\"quantity\"];\n if (executed.type === \"REMOVE_COLUMNS_ROWS\") {\n const executedElements = [...executed.elements].sort((a, b) => b - a);\n for (let removedElement of executedElements) {\n if (quantity > removedElement) {\n quantity--;\n }\n }\n }\n if (executed.type === \"ADD_COLUMNS_ROWS\") {\n const executedBase = executed.position === \"before\" ? executed.base - 1 : executed.base;\n quantity = quantity > executedBase ? quantity + executed.quantity : quantity;\n }\n return quantity > 0 ? { ...cmd, quantity } : undefined;\n }\n /**\n * Cancel CREATE_FILTER_TABLE and ADD_MERGE commands if they overlap a filter\n */\n function createTableTransformation(cmd, executed) {\n if (cmd.sheetId !== executed.sheetId) {\n return cmd;\n }\n for (const cmdTarget of cmd.target) {\n for (const executedCmdTarget of executed.target) {\n if (overlap(executedCmdTarget, cmdTarget)) {\n return undefined;\n }\n }\n }\n return cmd;\n }\n\n const transformations = [\n { match: isSheetDependent, fn: transformSheetId },\n { match: isTargetDependent, fn: transformTarget },\n { match: isPositionDependent, fn: transformPosition },\n { match: isGridDependent, fn: transformDimension },\n { match: isRangeDependant, fn: transformRangeData },\n ];\n /**\n * Get the result of applying the operation transformations on the given command\n * to transform based on the executed command.\n * Let's see a small example:\n * Given\n * - command A: set the content of C1 to \"Hello\"\n * - command B: add a column after A\n *\n * If command B has been executed locally and not transmitted (yet) to\n * other clients, and command A arrives from an other client to be executed locally.\n * Command A is no longer valid and no longer reflects the user intention.\n * It needs to be transformed knowing that command B is already executed.\n * transform(A, B) => set the content of D1 to \"Hello\"\n */\n function transform(toTransform, executed) {\n const specificTransform = otRegistry.getTransformation(toTransform.type, executed.type);\n return specificTransform\n ? specificTransform(toTransform, executed)\n : genericTransform(toTransform, executed);\n }\n /**\n * Get the result of applying the operation transformations on all the given\n * commands to transform for each executed commands.\n */\n function transformAll(toTransform, executed) {\n let transformedCommands = [...toTransform];\n for (const executedCommand of executed) {\n transformedCommands = transformedCommands\n .map((cmd) => transform(cmd, executedCommand))\n .filter(isDefined$1);\n }\n return transformedCommands;\n }\n /**\n * Apply all generic transformation based on the characteristic of the given commands.\n */\n function genericTransform(cmd, executed) {\n for (const { match, fn } of transformations) {\n if (match(cmd)) {\n const result = fn(cmd, executed);\n if (result === \"SKIP_TRANSFORMATION\") {\n continue;\n }\n if (result === \"IGNORE_COMMAND\") {\n return undefined;\n }\n cmd = result;\n }\n }\n return cmd;\n }\n function transformSheetId(cmd, executed) {\n const deleteSheet = executed.type === \"DELETE_SHEET\" && executed.sheetId;\n if (cmd.sheetId === deleteSheet) {\n return \"IGNORE_COMMAND\";\n }\n else if (cmd.type === \"CREATE_SHEET\" ||\n executed.type === \"CREATE_SHEET\" ||\n cmd.sheetId !== executed.sheetId) {\n return cmd;\n }\n return \"SKIP_TRANSFORMATION\";\n }\n function transformTarget(cmd, executed) {\n const transformSheetResult = transformSheetId(cmd, executed);\n if (transformSheetResult !== \"SKIP_TRANSFORMATION\") {\n return transformSheetResult === \"IGNORE_COMMAND\" ? \"IGNORE_COMMAND\" : cmd;\n }\n const target = [];\n for (const zone of cmd.target) {\n const newZone = transformZone(zone, executed);\n if (newZone) {\n target.push(newZone);\n }\n }\n if (!target.length) {\n return \"IGNORE_COMMAND\";\n }\n return { ...cmd, target };\n }\n function transformRangeData(cmd, executed) {\n const ranges = [];\n const deletedSheet = executed.type === \"DELETE_SHEET\" && executed.sheetId;\n for (const range of cmd.ranges) {\n if (range._sheetId !== executed.sheetId) {\n ranges.push({ ...range, _zone: range._zone });\n }\n else {\n const newZone = transformZone(range._zone, executed);\n if (newZone && deletedSheet !== range._sheetId) {\n ranges.push({ ...range, _zone: newZone });\n }\n }\n }\n if (!ranges.length) {\n return \"IGNORE_COMMAND\";\n }\n return { ...cmd, ranges };\n }\n function transformDimension(cmd, executed) {\n const transformSheetResult = transformSheetId(cmd, executed);\n if (transformSheetResult !== \"SKIP_TRANSFORMATION\") {\n return transformSheetResult === \"IGNORE_COMMAND\" ? \"IGNORE_COMMAND\" : cmd;\n }\n if (executed.type === \"ADD_COLUMNS_ROWS\" || executed.type === \"REMOVE_COLUMNS_ROWS\") {\n if (executed.dimension !== cmd.dimension) {\n return cmd;\n }\n const isUnique = cmd.type === \"ADD_COLUMNS_ROWS\";\n const field = isUnique ? \"base\" : \"elements\";\n let elements = isUnique ? [cmd[field]] : cmd[field];\n if (executed.type === \"REMOVE_COLUMNS_ROWS\") {\n elements = elements\n .map((element) => {\n if (executed.elements.includes(element)) {\n return undefined;\n }\n const executedElements = executed.elements.sort((a, b) => b - a);\n for (let removedElement of executedElements) {\n if (element > removedElement) {\n element--;\n }\n }\n return element;\n })\n .filter(isDefined$1);\n }\n if (executed.type === \"ADD_COLUMNS_ROWS\") {\n const base = executed.position === \"before\" ? executed.base - 1 : executed.base;\n elements = elements.map((el) => (el > base ? el + executed.quantity : el));\n }\n if (elements.length) {\n let result = elements;\n if (isUnique) {\n result = elements[0];\n }\n return { ...cmd, [field]: result };\n }\n return \"IGNORE_COMMAND\";\n }\n return \"SKIP_TRANSFORMATION\";\n }\n /**\n * Transform a PositionDependentCommand. It could be impacted by a grid command\n * (Add/remove cols/rows) and a merge\n */\n function transformPosition(cmd, executed) {\n const transformSheetResult = transformSheetId(cmd, executed);\n if (transformSheetResult !== \"SKIP_TRANSFORMATION\") {\n return transformSheetResult === \"IGNORE_COMMAND\" ? \"IGNORE_COMMAND\" : cmd;\n }\n if (executed.type === \"ADD_COLUMNS_ROWS\" || executed.type === \"REMOVE_COLUMNS_ROWS\") {\n return transformPositionWithGrid(cmd, executed);\n }\n if (executed.type === \"ADD_MERGE\") {\n return transformPositionWithMerge(cmd, executed);\n }\n return \"SKIP_TRANSFORMATION\";\n }\n /**\n * Transform a PositionDependentCommand after a grid shape modification. This\n * transformation consists of updating the position.\n */\n function transformPositionWithGrid(cmd, executed) {\n const field = executed.dimension === \"COL\" ? \"col\" : \"row\";\n let base = cmd[field];\n if (executed.type === \"REMOVE_COLUMNS_ROWS\") {\n const elements = [...executed.elements].sort((a, b) => b - a);\n if (elements.includes(base)) {\n return \"IGNORE_COMMAND\";\n }\n for (let removedElement of elements) {\n if (base >= removedElement) {\n base--;\n }\n }\n }\n if (executed.type === \"ADD_COLUMNS_ROWS\") {\n if (base > executed.base || (base === executed.base && executed.position === \"before\")) {\n base = base + executed.quantity;\n }\n }\n return { ...cmd, [field]: base };\n }\n /**\n * Transform a PositionDependentCommand after a merge. This transformation\n * consists of checking that the position is not inside the merged zones\n */\n function transformPositionWithMerge(cmd, executed) {\n for (const zone of executed.target) {\n const sameTopLeft = cmd.col === zone.left && cmd.row === zone.top;\n if (!sameTopLeft && isInside(cmd.col, cmd.row, zone)) {\n return \"IGNORE_COMMAND\";\n }\n }\n return cmd;\n }\n\n class Revision {\n /**\n * A revision represents a whole client action (Create a sheet, merge a Zone, Undo, ...).\n * A revision contains the following information:\n * - id: ID of the revision\n * - commands: CoreCommands that are linked to the action, and should be\n * dispatched in other clients\n * - clientId: Client who initiated the action\n * - changes: List of changes applied on the state.\n */\n constructor(id, clientId, commands, changes) {\n this._commands = [];\n this._changes = [];\n this.id = id;\n this.clientId = clientId;\n this._commands = [...commands];\n this._changes = changes ? [...changes] : [];\n }\n setChanges(changes) {\n this._changes = changes;\n }\n get commands() {\n return this._commands;\n }\n get changes() {\n return this._changes;\n }\n }\n\n class ClientDisconnectedError extends Error {\n }\n class Session extends EventBus {\n /**\n * Manages the collaboration between multiple users on the same spreadsheet.\n * It can forward local state changes to other users to ensure they all eventually\n * reach the same state.\n * It also manages the positions of each clients in the spreadsheet to provide\n * a visual indication of what other users are doing in the spreadsheet.\n *\n * @param revisions\n * @param transportService communication channel used to send and receive messages\n * between all connected clients\n * @param client the client connected locally\n * @param serverRevisionId\n */\n constructor(revisions, transportService, serverRevisionId = DEFAULT_REVISION_ID) {\n super();\n this.revisions = revisions;\n this.transportService = transportService;\n this.serverRevisionId = serverRevisionId;\n /**\n * Positions of the others client.\n */\n this.clients = {};\n this.clientId = \"local\";\n this.pendingMessages = [];\n this.waitingAck = false;\n /**\n * Flag used to block all commands when an undo or redo is triggered, until\n * it is accepted on the server\n */\n this.waitingUndoRedoAck = false;\n this.isReplayingInitialRevisions = false;\n this.processedRevisions = new Set();\n this.uuidGenerator = new UuidGenerator();\n this.debouncedMove = debounce(this._move.bind(this), DEBOUNCE_TIME);\n }\n canApplyOptimisticUpdate() {\n return !this.waitingUndoRedoAck;\n }\n /**\n * Add a new revision to the collaborative session.\n * It will be transmitted to all other connected clients.\n */\n save(commands, changes) {\n if (!commands.length || !changes.length || !this.canApplyOptimisticUpdate())\n return;\n const revision = new Revision(this.uuidGenerator.uuidv4(), this.clientId, commands, changes);\n this.revisions.append(revision.id, revision);\n this.trigger(\"new-local-state-update\", { id: revision.id });\n this.sendUpdateMessage({\n type: \"REMOTE_REVISION\",\n version: MESSAGE_VERSION,\n serverRevisionId: this.serverRevisionId,\n nextRevisionId: revision.id,\n clientId: revision.clientId,\n commands: revision.commands,\n });\n }\n undo(revisionId) {\n this.waitingUndoRedoAck = true;\n this.sendUpdateMessage({\n type: \"REVISION_UNDONE\",\n version: MESSAGE_VERSION,\n serverRevisionId: this.serverRevisionId,\n nextRevisionId: this.uuidGenerator.uuidv4(),\n undoneRevisionId: revisionId,\n });\n }\n redo(revisionId) {\n this.waitingUndoRedoAck = true;\n this.sendUpdateMessage({\n type: \"REVISION_REDONE\",\n version: MESSAGE_VERSION,\n serverRevisionId: this.serverRevisionId,\n nextRevisionId: this.uuidGenerator.uuidv4(),\n redoneRevisionId: revisionId,\n });\n }\n /**\n * Notify that the position of the client has changed\n */\n move(position) {\n this.debouncedMove(position);\n }\n join(client) {\n if (client) {\n this.clients[client.id] = client;\n this.clientId = client.id;\n }\n else {\n this.clients[\"local\"] = { id: \"local\", name: \"local\" };\n this.clientId = \"local\";\n }\n this.transportService.onNewMessage(this.clientId, this.onMessageReceived.bind(this));\n }\n loadInitialMessages(messages) {\n this.isReplayingInitialRevisions = true;\n this.on(\"unexpected-revision-id\", this, ({ revisionId }) => {\n throw new Error(`The spreadsheet could not be loaded. Revision ${revisionId} is corrupted.`);\n });\n for (const message of messages) {\n this.onMessageReceived(message);\n }\n this.off(\"unexpected-revision-id\", this);\n this.isReplayingInitialRevisions = false;\n }\n /**\n * Notify the server that the user client left the collaborative session\n */\n leave() {\n delete this.clients[this.clientId];\n this.transportService.leave(this.clientId);\n this.transportService.sendMessage({\n type: \"CLIENT_LEFT\",\n clientId: this.clientId,\n version: MESSAGE_VERSION,\n });\n }\n /**\n * Send a snapshot of the spreadsheet to the collaboration server\n */\n snapshot(data) {\n const snapshotId = this.uuidGenerator.uuidv4();\n this.transportService.sendMessage({\n type: \"SNAPSHOT\",\n nextRevisionId: snapshotId,\n serverRevisionId: this.serverRevisionId,\n data: { ...data, revisionId: snapshotId },\n version: MESSAGE_VERSION,\n });\n }\n getClient() {\n const client = this.clients[this.clientId];\n if (!client) {\n throw new ClientDisconnectedError(\"The client left the session\");\n }\n return client;\n }\n getConnectedClients() {\n return new Set(Object.values(this.clients).filter(isDefined$1));\n }\n getRevisionId() {\n return this.serverRevisionId;\n }\n isFullySynchronized() {\n return this.pendingMessages.length === 0;\n }\n _move(position) {\n var _a;\n // this method is debounced and might be called after the client\n // left the session.\n if (!this.clients[this.clientId])\n return;\n const currentPosition = (_a = this.clients[this.clientId]) === null || _a === void 0 ? void 0 : _a.position;\n if ((currentPosition === null || currentPosition === void 0 ? void 0 : currentPosition.col) === position.col &&\n currentPosition.row === position.row &&\n currentPosition.sheetId === position.sheetId) {\n return;\n }\n const type = currentPosition ? \"CLIENT_MOVED\" : \"CLIENT_JOINED\";\n const client = this.getClient();\n this.clients[this.clientId] = { ...client, position };\n this.transportService.sendMessage({\n type,\n version: MESSAGE_VERSION,\n client: { ...client, position },\n });\n }\n /**\n * Handles messages received from other clients in the collaborative\n * session.\n */\n onMessageReceived(message) {\n if (this.isAlreadyProcessed(message))\n return;\n switch (message.type) {\n case \"CLIENT_MOVED\":\n this.onClientMoved(message);\n break;\n case \"CLIENT_JOINED\":\n this.onClientJoined(message);\n break;\n case \"CLIENT_LEFT\":\n this.onClientLeft(message);\n break;\n case \"REVISION_REDONE\": {\n this.revisions.redo(message.redoneRevisionId, message.nextRevisionId, message.serverRevisionId);\n this.trigger(\"revision-redone\", {\n revisionId: message.redoneRevisionId,\n commands: this.revisions.get(message.redoneRevisionId).commands,\n });\n break;\n }\n case \"REVISION_UNDONE\":\n this.revisions.undo(message.undoneRevisionId, message.nextRevisionId, message.serverRevisionId);\n this.trigger(\"revision-undone\", {\n revisionId: message.undoneRevisionId,\n commands: this.revisions.get(message.undoneRevisionId).commands,\n });\n break;\n case \"REMOTE_REVISION\":\n if (message.serverRevisionId !== this.serverRevisionId) {\n this.trigger(\"unexpected-revision-id\", { revisionId: message.serverRevisionId });\n return;\n }\n const { clientId, commands } = message;\n const revision = new Revision(message.nextRevisionId, clientId, commands);\n if (revision.clientId !== this.clientId) {\n this.revisions.insert(revision.id, revision, message.serverRevisionId);\n const pendingCommands = this.pendingMessages\n .filter((msg) => msg.type === \"REMOTE_REVISION\")\n .map((msg) => msg.commands)\n .flat();\n this.trigger(\"remote-revision-received\", {\n commands: transformAll(commands, pendingCommands),\n });\n }\n break;\n case \"SNAPSHOT_CREATED\": {\n const revision = new Revision(message.nextRevisionId, \"server\", []);\n this.revisions.insert(revision.id, revision, message.serverRevisionId);\n this.dropPendingHistoryMessages();\n this.trigger(\"snapshot\");\n break;\n }\n }\n this.acknowledge(message);\n this.trigger(\"collaborative-event-received\");\n }\n onClientMoved(message) {\n if (message.client.id !== this.clientId) {\n this.clients[message.client.id] = message.client;\n }\n }\n /**\n * Register the new client and send your\n * own position back.\n */\n onClientJoined(message) {\n if (message.client.id !== this.clientId) {\n this.clients[message.client.id] = message.client;\n const client = this.clients[this.clientId];\n if (client) {\n const { position } = client;\n if (position) {\n this.transportService.sendMessage({\n type: \"CLIENT_MOVED\",\n version: MESSAGE_VERSION,\n client: { ...client, position },\n });\n }\n }\n }\n }\n onClientLeft(message) {\n if (message.clientId !== this.clientId) {\n delete this.clients[message.clientId];\n }\n }\n sendUpdateMessage(message) {\n this.pendingMessages.push(message);\n if (this.waitingAck) {\n return;\n }\n this.waitingAck = true;\n this.sendPendingMessage();\n }\n /**\n * Send the next pending message\n */\n sendPendingMessage() {\n let message = this.pendingMessages[0];\n if (!message)\n return;\n if (message.type === \"REMOTE_REVISION\") {\n const revision = this.revisions.get(message.nextRevisionId);\n if (revision.commands.length === 0) {\n /**\n * The command is empty, we have to drop all the next local revisions\n * to avoid issues with undo/redo\n */\n this.revisions.drop(revision.id);\n const revisionIds = this.pendingMessages\n .filter((message) => message.type === \"REMOTE_REVISION\")\n .map((message) => message.nextRevisionId);\n this.trigger(\"pending-revisions-dropped\", { revisionIds });\n this.waitingAck = false;\n this.waitingUndoRedoAck = false;\n this.pendingMessages = [];\n return;\n }\n message = {\n ...message,\n clientId: revision.clientId,\n commands: revision.commands,\n };\n }\n if (this.isReplayingInitialRevisions) {\n throw new Error(`Trying to send a new revision while replaying initial revision. This can lead to endless dispatches every time the spreadsheet is open.\n ${JSON.stringify(message)}`);\n }\n this.transportService.sendMessage({\n ...message,\n serverRevisionId: this.serverRevisionId,\n });\n }\n acknowledge(message) {\n if (message.type === \"REVISION_UNDONE\" || message.type === \"REVISION_REDONE\") {\n this.waitingUndoRedoAck = false;\n }\n switch (message.type) {\n case \"REMOTE_REVISION\":\n case \"REVISION_REDONE\":\n case \"REVISION_UNDONE\":\n case \"SNAPSHOT_CREATED\":\n this.waitingAck = false;\n this.pendingMessages = this.pendingMessages.filter((msg) => msg.nextRevisionId !== message.nextRevisionId);\n this.serverRevisionId = message.nextRevisionId;\n this.processedRevisions.add(message.nextRevisionId);\n this.sendPendingMessage();\n break;\n }\n }\n isAlreadyProcessed(message) {\n if (message.type === \"CLIENT_MOVED\" && message.client.id === this.clientId) {\n return true;\n }\n switch (message.type) {\n case \"REMOTE_REVISION\":\n case \"REVISION_REDONE\":\n case \"REVISION_UNDONE\":\n return this.processedRevisions.has(message.nextRevisionId);\n default:\n return false;\n }\n }\n dropPendingHistoryMessages() {\n this.waitingUndoRedoAck = false;\n this.pendingMessages = this.pendingMessages.filter(({ type }) => type !== \"REVISION_REDONE\" && type !== \"REVISION_UNDONE\");\n }\n }\n\n function randomChoice(arr) {\n return arr[Math.floor(Math.random() * arr.length)];\n }\n const colors = [\n \"#ff851b\",\n \"#0074d9\",\n \"#7fdbff\",\n \"#b10dc9\",\n \"#39cccc\",\n \"#f012be\",\n \"#3d9970\",\n \"#111111\",\n \"#ff4136\",\n \"#aaaaaa\",\n \"#85144b\",\n \"#001f3f\",\n ];\n class SelectionMultiUserPlugin extends UIPlugin {\n constructor() {\n super(...arguments);\n this.availableColors = new Set(colors);\n this.colors = {};\n }\n isPositionValid(position) {\n return (position.row < this.getters.getNumberRows(position.sheetId) &&\n position.col < this.getters.getNumberCols(position.sheetId));\n }\n chooseNewColor() {\n if (this.availableColors.size === 0) {\n this.availableColors = new Set(colors);\n }\n const color = randomChoice([...this.availableColors.values()]);\n this.availableColors.delete(color);\n return color;\n }\n /**\n * Get the list of others connected clients which are present in the same sheet\n * and with a valid position\n */\n getClientsToDisplay() {\n try {\n this.getters.getClient();\n }\n catch (e) {\n if (e instanceof ClientDisconnectedError) {\n return [];\n }\n else {\n throw e;\n }\n }\n const sheetId = this.getters.getActiveSheetId();\n const clients = [];\n for (const client of this.getters.getConnectedClients()) {\n if (client.id !== this.getters.getClient().id &&\n client.position &&\n client.position.sheetId === sheetId &&\n this.isPositionValid(client.position)) {\n const position = client.position;\n if (!this.colors[client.id]) {\n this.colors[client.id] = this.chooseNewColor();\n }\n const color = this.colors[client.id];\n clients.push({ ...client, position, color });\n }\n }\n return clients;\n }\n drawGrid(renderingContext) {\n if (this.getters.isDashboard()) {\n return;\n }\n const { ctx, thinLineWidth } = renderingContext;\n const activeSheetId = this.getters.getActiveSheetId();\n for (const client of this.getClientsToDisplay()) {\n const { row, col } = client.position;\n const zone = this.getters.expandZone(activeSheetId, {\n top: row,\n bottom: row,\n left: col,\n right: col,\n });\n const { x, y, width, height } = this.getters.getVisibleRect(zone);\n if (width <= 0 || height <= 0) {\n continue;\n }\n const color = client.color;\n /* Cell background */\n const cellBackgroundColor = `${color}10`;\n ctx.fillStyle = cellBackgroundColor;\n ctx.lineWidth = 4 * thinLineWidth;\n ctx.strokeStyle = color;\n ctx.globalCompositeOperation = \"multiply\";\n ctx.fillRect(x, y, width, height);\n /* Cell border */\n ctx.globalCompositeOperation = \"source-over\";\n ctx.strokeRect(x, y, width, height);\n /* client name background */\n ctx.font = `bold ${DEFAULT_FONT_SIZE + 1}px ${DEFAULT_FONT}`;\n }\n }\n }\n SelectionMultiUserPlugin.getters = [\"getClientsToDisplay\"];\n SelectionMultiUserPlugin.layers = [6 /* LAYERS.Selection */];\n\n class SortPlugin extends UIPlugin {\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"SORT_CELLS\":\n if (!isInside(cmd.col, cmd.row, cmd.zone)) {\n throw new Error(_lt(\"The anchor must be part of the provided zone\"));\n }\n return this.checkValidations(cmd, this.checkMerge, this.checkMergeSizes);\n }\n return 0 /* CommandResult.Success */;\n }\n handle(cmd) {\n switch (cmd.type) {\n case \"SORT_CELLS\":\n this.sortZone(cmd.sheetId, cmd, cmd.zone, cmd.sortDirection, cmd.sortOptions || {});\n break;\n }\n }\n checkMerge({ sheetId, zone }) {\n if (!this.getters.doesIntersectMerge(sheetId, zone)) {\n return 0 /* CommandResult.Success */;\n }\n /*Test the presence of single cells*/\n const singleCells = positions(zone).some(({ col, row }) => !this.getters.isInMerge({ sheetId, col, row }));\n if (singleCells) {\n return 63 /* CommandResult.InvalidSortZone */;\n }\n return 0 /* CommandResult.Success */;\n }\n checkMergeSizes({ sheetId, zone }) {\n if (!this.getters.doesIntersectMerge(sheetId, zone)) {\n return 0 /* CommandResult.Success */;\n }\n const merges = this.getters.getMerges(sheetId).filter((merge) => overlap(merge, zone));\n /*Test the presence of merges of different sizes*/\n const mergeDimension = zoneToDimension(merges[0]);\n let [widthFirst, heightFirst] = [mergeDimension.width, mergeDimension.height];\n if (!merges.every((merge) => {\n let [widthCurrent, heightCurrent] = [\n merge.right - merge.left + 1,\n merge.bottom - merge.top + 1,\n ];\n return widthCurrent === widthFirst && heightCurrent === heightFirst;\n })) {\n return 63 /* CommandResult.InvalidSortZone */;\n }\n return 0 /* CommandResult.Success */;\n }\n // getContiguousZone helpers\n /**\n * safe-version of expandZone to make sure we don't get out of the grid\n */\n expand(sheetId, z) {\n const { left, right, top, bottom } = this.getters.expandZone(sheetId, z);\n return {\n left: Math.max(0, left),\n right: Math.min(this.getters.getNumberCols(sheetId) - 1, right),\n top: Math.max(0, top),\n bottom: Math.min(this.getters.getNumberRows(sheetId) - 1, bottom),\n };\n }\n /**\n * verifies the presence of at least one non-empty cell in the given zone\n */\n checkExpandedValues(sheetId, z) {\n const expandedZone = this.expand(sheetId, z);\n let cell;\n if (this.getters.doesIntersectMerge(sheetId, expandedZone)) {\n const { left, right, top, bottom } = expandedZone;\n for (let c = left; c <= right; c++) {\n for (let r = top; r <= bottom; r++) {\n const { col, row } = this.getters.getMainCellPosition({ sheetId, col: c, row: r });\n cell = this.getters.getEvaluatedCell({ sheetId, col, row });\n if (cell.formattedValue) {\n return true;\n }\n }\n }\n }\n else {\n for (let cell of this.getters.getEvaluatedCellsInZone(sheetId, expandedZone)) {\n if (cell.formattedValue) {\n return true;\n }\n }\n }\n return false;\n }\n /**\n * This function will expand the provided zone in directions (top, bottom, left, right) for which there\n * are non-null cells on the external boundary of the zone in the given direction.\n *\n * Example:\n * A B C D E\n * ___ ___ ___ ___ ___\n * 1 | | D | | | |\n * ___ ___ ___ ___ ___\n * 2 | 5 | | 1 | D | |\n * ___ ___ ___ ___ ___\n * 3 | | | A | X | |\n * ___ ___ ___ ___ ___\n * 4 | | | | | |\n * ___ ___ ___ ___ ___\n *\n * Let's consider a provided zone corresponding to (C2:D3) - (left:2, right: 3, top:1, bottom:2)\n * - the top external boundary is (B1:E1)\n * Since we have B1='D' != \"\", we expand to the top: => (C1:D3)\n * The top boundary having reached the top of the grid, we cannot expand in that direction anymore\n *\n * - the left boundary is (B1:B4)\n * since we have B1 again, we expand to the left => (B1:D3)\n *\n * - the right and bottom boundaries are a dead end for now as (E1:E4) and (A4:E4) are empty.\n *\n * - the left boundary is now (A1:A4)\n * Since we have A2=5 != \"\", we can therefore expand to the left => (A1:D3)\n *\n * This will be the final zone as left and top have reached the boundaries of the grid and\n * the other boundaries (E1:E4) and (A4:E4) are empty.\n *\n * @param sheetId UID of concerned sheet\n * @param zone Zone\n *\n */\n getContiguousZone(sheetId, zone) {\n let { top, bottom, left, right } = zone;\n let canExpand;\n let stop = false;\n while (!stop) {\n stop = true;\n /** top row external boundary */\n if (top > 0) {\n canExpand = this.checkExpandedValues(sheetId, {\n left: left - 1,\n right: right + 1,\n top: top - 1,\n bottom: top - 1,\n });\n if (canExpand) {\n stop = false;\n top--;\n }\n }\n /** left column external boundary */\n if (left > 0) {\n canExpand = this.checkExpandedValues(sheetId, {\n left: left - 1,\n right: left - 1,\n top: top - 1,\n bottom: bottom + 1,\n });\n if (canExpand) {\n stop = false;\n left--;\n }\n }\n /** right column external boundary */\n if (right < this.getters.getNumberCols(sheetId) - 1) {\n canExpand = this.checkExpandedValues(sheetId, {\n left: right + 1,\n right: right + 1,\n top: top - 1,\n bottom: bottom + 1,\n });\n if (canExpand) {\n stop = false;\n right++;\n }\n }\n /** bottom row external boundary */\n if (bottom < this.getters.getNumberRows(sheetId) - 1) {\n canExpand = this.checkExpandedValues(sheetId, {\n left: left - 1,\n right: right + 1,\n top: bottom + 1,\n bottom: bottom + 1,\n });\n if (canExpand) {\n stop = false;\n bottom++;\n }\n }\n }\n return { left, right, top, bottom };\n }\n /**\n * This function evaluates if the top row of a provided zone can be considered as a `header`\n * by checking the following criteria:\n * * If the left-most column top row value (topLeft) is empty, we ignore it while evaluating the criteria.\n * 1 - Apart from the left-most column, every element of the top row must be non-empty, i.e. a cell should be present in the sheet.\n * 2 - There should be at least one column in which the type (CellValueType) of the rop row cell differs from the type of the cell below.\n * For the second criteria, we ignore columns on which the cell below is empty.\n *\n */\n hasHeader(sheetId, items) {\n if (items[0].length === 1)\n return false;\n let cells = items.map((col) => col.map(({ col, row }) => this.getters.getEvaluatedCell({ sheetId, col, row }).type));\n // ignore left-most column when topLeft cell is empty\n const topLeft = cells[0][0];\n if (topLeft === CellValueType.empty) {\n cells = cells.slice(1);\n }\n if (cells.some((item) => item[0] === CellValueType.empty)) {\n return false;\n }\n else if (cells.some((item) => item[1] !== CellValueType.empty && item[0] !== item[1])) {\n return true;\n }\n else {\n return false;\n }\n }\n sortZone(sheetId, anchor, zone, sortDirection, options) {\n const [stepX, stepY] = this.mainCellsSteps(sheetId, zone);\n let sortingCol = this.getters.getMainCellPosition({\n sheetId,\n col: anchor.col,\n row: anchor.row,\n }).col; // fetch anchor\n let sortZone = Object.assign({}, zone);\n // Update in case of merges in the zone\n let cellPositions = this.mainCells(sheetId, zone);\n if (!options.sortHeaders && this.hasHeader(sheetId, cellPositions)) {\n sortZone.top += stepY;\n }\n cellPositions = this.mainCells(sheetId, sortZone);\n const sortingCells = cellPositions[sortingCol - sortZone.left];\n const sortedIndexOfSortTypeCells = sortCells(sortingCells.map((position) => this.getters.getEvaluatedCell(position)), sortDirection, Boolean(options.emptyCellAsZero));\n const sortedIndex = sortedIndexOfSortTypeCells.map((x) => x.index);\n const [width, height] = [cellPositions.length, cellPositions[0].length];\n const updateCellCommands = [];\n for (let c = 0; c < width; c++) {\n for (let r = 0; r < height; r++) {\n let { col, row, sheetId } = cellPositions[c][sortedIndex[r]];\n const cell = this.getters.getCell({ sheetId, col, row });\n let newCol = sortZone.left + c * stepX;\n let newRow = sortZone.top + r * stepY;\n let newCellValues = {\n sheetId: sheetId,\n col: newCol,\n row: newRow,\n content: \"\",\n };\n if (cell) {\n let content = cell.content;\n if (cell.isFormula) {\n const position = this.getters.getCellPosition(cell.id);\n const offsetY = newRow - position.row;\n // we only have a vertical offset\n const ranges = this.getters.createAdaptedRanges(cell.dependencies, 0, offsetY, sheetId);\n content = this.getters.buildFormulaContent(sheetId, cell, ranges);\n }\n newCellValues.style = cell.style;\n newCellValues.content = content;\n newCellValues.format = cell.format;\n }\n updateCellCommands.push(newCellValues);\n }\n for (const cmd of updateCellCommands) {\n this.dispatch(\"UPDATE_CELL\", cmd);\n }\n }\n }\n /**\n * Return the distances between main merge cells in the zone.\n * (1 if there are no merges).\n * Note: it is assumed all merges are the same in the zone.\n */\n mainCellsSteps(sheetId, zone) {\n const merge = this.getters.getMerge({ sheetId, col: zone.left, row: zone.top });\n const stepX = merge ? merge.right - merge.left + 1 : 1;\n const stepY = merge ? merge.bottom - merge.top + 1 : 1;\n return [stepX, stepY];\n }\n /**\n * Return a 2D array of cells in the zone (main merge cells if there are merges)\n */\n mainCells(sheetId, zone) {\n const [stepX, stepY] = this.mainCellsSteps(sheetId, zone);\n const cells = [];\n const cols = range(zone.left, zone.right + 1, stepX);\n const rows = range(zone.top, zone.bottom + 1, stepY);\n for (const col of cols) {\n const colCells = [];\n cells.push(colCells);\n for (const row of rows) {\n colCells.push({ sheetId, col, row });\n }\n }\n return cells;\n }\n }\n SortPlugin.getters = [\"getContiguousZone\"];\n\n class UIOptionsPlugin extends UIPlugin {\n constructor() {\n super(...arguments);\n this.showFormulas = false;\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n handle(cmd) {\n switch (cmd.type) {\n case \"SET_FORMULA_VISIBILITY\":\n this.showFormulas = cmd.show;\n break;\n }\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n shouldShowFormulas() {\n return this.showFormulas;\n }\n }\n UIOptionsPlugin.getters = [\"shouldShowFormulas\"];\n\n class SheetUIPlugin extends UIPlugin {\n constructor() {\n super(...arguments);\n this.ctx = document.createElement(\"canvas\").getContext(\"2d\");\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"AUTORESIZE_ROWS\":\n case \"AUTORESIZE_COLUMNS\":\n try {\n this.getters.getSheet(cmd.sheetId);\n break;\n }\n catch (error) {\n return 27 /* CommandResult.InvalidSheetId */;\n }\n }\n return 0 /* CommandResult.Success */;\n }\n handle(cmd) {\n switch (cmd.type) {\n case \"AUTORESIZE_COLUMNS\":\n for (let col of cmd.cols) {\n const size = this.getColMaxWidth(cmd.sheetId, col);\n if (size !== 0) {\n this.dispatch(\"RESIZE_COLUMNS_ROWS\", {\n elements: [col],\n dimension: \"COL\",\n size,\n sheetId: cmd.sheetId,\n });\n }\n }\n break;\n case \"AUTORESIZE_ROWS\":\n for (let row of cmd.rows) {\n this.dispatch(\"RESIZE_COLUMNS_ROWS\", {\n elements: [row],\n dimension: \"ROW\",\n size: null,\n sheetId: cmd.sheetId,\n });\n }\n break;\n }\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n getCellWidth(position) {\n let contentWidth = this.getTextWidth(position);\n const icon = this.getters.getConditionalIcon(position);\n if (icon) {\n contentWidth += computeIconWidth(this.getters.getCellStyle(position));\n }\n const isFilterHeader = this.getters.isFilterHeader(position);\n if (isFilterHeader) {\n contentWidth += ICON_EDGE_LENGTH + FILTER_ICON_MARGIN;\n }\n if (contentWidth > 0) {\n contentWidth += 2 * PADDING_AUTORESIZE_HORIZONTAL;\n if (this.getters.getCellStyle(position).wrapping === \"wrap\") {\n const colWidth = this.getters.getColSize(this.getters.getActiveSheetId(), position.col);\n return Math.min(colWidth, contentWidth);\n }\n }\n return contentWidth;\n }\n getTextWidth(position) {\n const text = this.getters.getCellText(position, this.getters.shouldShowFormulas());\n return computeTextWidth(this.ctx, text, this.getters.getCellComputedStyle(position));\n }\n getCellText(position, showFormula = false) {\n const cell = this.getters.getCell(position);\n if (showFormula && (cell === null || cell === void 0 ? void 0 : cell.isFormula)) {\n return cell.content;\n }\n else {\n return this.getters.getEvaluatedCell(position).formattedValue;\n }\n }\n getCellMultiLineText(position, width) {\n const style = this.getters.getCellStyle(position);\n const text = this.getters.getCellText(position);\n const words = text.split(\" \");\n const brokenText = [];\n let textLine = \"\";\n let availableWidth = width;\n for (let word of words) {\n const splitWord = this.splitWordToSpecificWidth(this.ctx, word, width, style);\n const lastPart = splitWord.pop();\n const lastPartWidth = computeTextWidth(this.ctx, lastPart, style);\n // At this step: \"splitWord\" is an array composed of parts of word whose\n // length is at most equal to \"width\".\n // Last part contains the end of the word.\n // Note that: When word length is less than width, then lastPart is equal\n // to word and splitWord is empty\n if (splitWord.length) {\n if (textLine !== \"\") {\n brokenText.push(textLine);\n textLine = \"\";\n availableWidth = width;\n }\n splitWord.forEach((wordPart) => {\n brokenText.push(wordPart);\n });\n textLine = lastPart;\n availableWidth = width - lastPartWidth;\n }\n else {\n // here \"lastPart\" is equal to \"word\" and the \"word\" size is smaller than \"width\"\n const _word = textLine === \"\" ? lastPart : \" \" + lastPart;\n const wordWidth = computeTextWidth(this.ctx, _word, style);\n if (wordWidth <= availableWidth) {\n textLine += _word;\n availableWidth -= wordWidth;\n }\n else {\n brokenText.push(textLine);\n textLine = lastPart;\n availableWidth = width - lastPartWidth;\n }\n }\n }\n if (textLine !== \"\") {\n brokenText.push(textLine);\n }\n return brokenText;\n }\n /**\n * Returns the size, start and end coordinates of a column on an unfolded sheet\n */\n getColDimensions(sheetId, col) {\n const start = this.getColRowOffset(\"COL\", 0, col, sheetId);\n const size = this.getters.getColSize(sheetId, col);\n const isColHidden = this.getters.isColHidden(sheetId, col);\n return {\n start,\n size,\n end: start + (isColHidden ? 0 : size),\n };\n }\n /**\n * Returns the size, start and end coordinates of a row an unfolded sheet\n */\n getRowDimensions(sheetId, row) {\n const start = this.getColRowOffset(\"ROW\", 0, row, sheetId);\n const size = this.getters.getRowSize(sheetId, row);\n const isRowHidden = this.getters.isRowHidden(sheetId, row);\n return {\n start,\n size: size,\n end: start + (isRowHidden ? 0 : size),\n };\n }\n /**\n * Returns the offset of a header (determined by the dimension) at the given index\n * based on the referenceIndex given. If start === 0, this method will return\n * the start attribute of the header.\n *\n * i.e. The size from A to B is the distance between A.start and B.end\n */\n getColRowOffset(dimension, referenceIndex, index, sheetId = this.getters.getActiveSheetId()) {\n if (index < referenceIndex) {\n return -this.getColRowOffset(dimension, index, referenceIndex);\n }\n let offset = 0;\n for (let i = referenceIndex; i < index; i++) {\n if (this.getters.isHeaderHidden(sheetId, dimension, i)) {\n continue;\n }\n offset +=\n dimension === \"COL\"\n ? this.getters.getColSize(sheetId, i)\n : this.getters.getRowSize(sheetId, i);\n }\n return offset;\n }\n // ---------------------------------------------------------------------------\n // Grid manipulation\n // ---------------------------------------------------------------------------\n getColMaxWidth(sheetId, index) {\n const cellsPositions = positions(this.getters.getColsZone(sheetId, index, index));\n const sizes = cellsPositions.map((position) => this.getCellWidth({ sheetId, ...position }));\n return Math.max(0, ...sizes);\n }\n splitWordToSpecificWidth(ctx, word, width, style) {\n const wordWidth = computeTextWidth(ctx, word, style);\n if (wordWidth <= width) {\n return [word];\n }\n const splitWord = [];\n let wordPart = \"\";\n for (let l of word) {\n const wordPartWidth = computeTextWidth(ctx, wordPart + l, style);\n if (wordPartWidth > width) {\n splitWord.push(wordPart);\n wordPart = l;\n }\n else {\n wordPart += l;\n }\n }\n splitWord.push(wordPart);\n return splitWord;\n }\n }\n SheetUIPlugin.getters = [\n \"getCellWidth\",\n \"getTextWidth\",\n \"getCellText\",\n \"getCellMultiLineText\",\n \"getColDimensions\",\n \"getRowDimensions\",\n \"getColRowOffset\",\n ];\n\n /** Abstract state of the clipboard when copying/cutting content that is pasted in cells of the sheet */\n class ClipboardCellsAbstractState {\n constructor(operation, getters, dispatch, selection) {\n this.getters = getters;\n this.dispatch = dispatch;\n this.selection = selection;\n this.operation = operation;\n this.sheetId = getters.getActiveSheetId();\n }\n isCutAllowed(target) {\n return 0 /* CommandResult.Success */;\n }\n isPasteAllowed(target, clipboardOption) {\n return 0 /* CommandResult.Success */;\n }\n /**\n * Add columns and/or rows to ensure that col + width and row + height are still\n * in the sheet\n */\n addMissingDimensions(width, height, col, row) {\n const sheetId = this.getters.getActiveSheetId();\n const missingRows = height + row - this.getters.getNumberRows(sheetId);\n if (missingRows > 0) {\n this.dispatch(\"ADD_COLUMNS_ROWS\", {\n dimension: \"ROW\",\n base: this.getters.getNumberRows(sheetId) - 1,\n sheetId,\n quantity: missingRows,\n position: \"after\",\n });\n }\n const missingCols = width + col - this.getters.getNumberCols(sheetId);\n if (missingCols > 0) {\n this.dispatch(\"ADD_COLUMNS_ROWS\", {\n dimension: \"COL\",\n base: this.getters.getNumberCols(sheetId) - 1,\n sheetId,\n quantity: missingCols,\n position: \"after\",\n });\n }\n }\n isColRowDirtyingClipboard(position, dimension) {\n return false;\n }\n drawClipboard(renderingContext) { }\n }\n\n /** State of the clipboard when copying/cutting cells */\n class ClipboardCellsState extends ClipboardCellsAbstractState {\n constructor(zones, operation, getters, dispatch, selection) {\n super(operation, getters, dispatch, selection);\n if (!zones.length) {\n this.cells = [[]];\n this.zones = [];\n this.copiedTables = [];\n return;\n }\n const lefts = new Set(zones.map((z) => z.left));\n const rights = new Set(zones.map((z) => z.right));\n const tops = new Set(zones.map((z) => z.top));\n const bottoms = new Set(zones.map((z) => z.bottom));\n const areZonesCompatible = (tops.size === 1 && bottoms.size === 1) || (lefts.size === 1 && rights.size === 1);\n // In order to don't paste several times the same cells in intersected zones\n // --> we merge zones that have common cells\n const clippedZones = areZonesCompatible\n ? mergeOverlappingZones(zones)\n : [zones[zones.length - 1]];\n const cellsPosition = clippedZones.map((zone) => positions(zone)).flat();\n const columnsIndex = [...new Set(cellsPosition.map((p) => p.col))].sort((a, b) => a - b);\n const rowsIndex = [...new Set(cellsPosition.map((p) => p.row))].sort((a, b) => a - b);\n const cellsInClipboard = [];\n const sheetId = getters.getActiveSheetId();\n for (let row of rowsIndex) {\n let cellsInRow = [];\n for (let col of columnsIndex) {\n const position = { col, row, sheetId };\n cellsInRow.push({\n cell: getters.getCell(position),\n style: getters.getCellComputedStyle(position),\n evaluatedCell: getters.getEvaluatedCell(position),\n border: getters.getCellBorder(position) || undefined,\n position,\n });\n }\n cellsInClipboard.push(cellsInRow);\n }\n const tables = [];\n for (const zone of zones) {\n for (const table of this.getters.getFilterTablesInZone(sheetId, zone)) {\n const values = [];\n for (const col of range(table.zone.left, table.zone.right + 1)) {\n values.push(this.getters.getFilterValues({ sheetId, col, row: table.zone.top }));\n }\n tables.push({ filtersValues: values, zone: table.zone });\n }\n }\n this.cells = cellsInClipboard;\n this.zones = clippedZones;\n this.copiedTables = tables;\n }\n isCutAllowed(target) {\n if (target.length !== 1) {\n return 19 /* CommandResult.WrongCutSelection */;\n }\n return 0 /* CommandResult.Success */;\n }\n isPasteAllowed(target, clipboardOption) {\n const sheetId = this.getters.getActiveSheetId();\n if (this.operation === \"CUT\" && (clipboardOption === null || clipboardOption === void 0 ? void 0 : clipboardOption.pasteOption) !== undefined) {\n // cannot paste only format or only value if the previous operation is a CUT\n return 21 /* CommandResult.WrongPasteOption */;\n }\n if (target.length > 1) {\n // cannot paste if we have a clipped zone larger than a cell and multiple\n // zones selected\n if (this.cells.length > 1 || this.cells[0].length > 1) {\n return 20 /* CommandResult.WrongPasteSelection */;\n }\n }\n const clipboardHeight = this.cells.length;\n const clipboardWidth = this.cells[0].length;\n for (let zone of this.getPasteZones(target)) {\n if (this.getters.doesIntersectMerge(sheetId, zone)) {\n if (target.length > 1 ||\n !this.getters.isSingleCellOrMerge(sheetId, target[0]) ||\n clipboardHeight * clipboardWidth !== 1) {\n return 2 /* CommandResult.WillRemoveExistingMerge */;\n }\n }\n }\n const { xSplit, ySplit } = this.getters.getPaneDivisions(sheetId);\n for (const zone of this.getPasteZones(target)) {\n if ((zone.left < xSplit && zone.right >= xSplit) ||\n (zone.top < ySplit && zone.bottom >= ySplit)) {\n return 75 /* CommandResult.FrozenPaneOverlap */;\n }\n }\n return 0 /* CommandResult.Success */;\n }\n /**\n * Paste the clipboard content in the given target\n */\n paste(target, options) {\n if (this.operation === \"COPY\") {\n this.pasteFromCopy(target, options);\n }\n else {\n this.pasteFromCut(target, options);\n }\n const height = this.cells.length;\n const width = this.cells[0].length;\n const isCutOperation = this.operation === \"CUT\";\n if (options === null || options === void 0 ? void 0 : options.selectTarget) {\n this.selectPastedZone(width, height, isCutOperation, target);\n }\n }\n pasteFromCopy(target, options) {\n if (target.length === 1) {\n // in this specific case, due to the isPasteAllowed function:\n // state.cells can contains several cells.\n // So if the target zone is larger than the copied zone,\n // we duplicate each cells as many times as possible to fill the zone.\n const height = this.cells.length;\n const width = this.cells[0].length;\n const pasteZones = this.pastedZones(target, width, height);\n for (const zone of pasteZones) {\n this.pasteZone(zone.left, zone.top, options);\n }\n }\n else {\n // in this case, due to the isPasteAllowed function: state.cells contains\n // only one cell\n for (const zone of target) {\n for (let col = zone.left; col <= zone.right; col++) {\n for (let row = zone.top; row <= zone.bottom; row++) {\n this.pasteZone(col, row, options);\n }\n }\n }\n }\n if ((options === null || options === void 0 ? void 0 : options.pasteOption) === undefined) {\n this.pasteCopiedTables(target);\n }\n }\n pasteFromCut(target, options) {\n this.clearClippedZones();\n const selection = target[0];\n this.pasteZone(selection.left, selection.top, options);\n this.dispatch(\"MOVE_RANGES\", {\n target: this.zones,\n sheetId: this.sheetId,\n targetSheetId: this.getters.getActiveSheetId(),\n col: selection.left,\n row: selection.top,\n });\n for (const filterTable of this.copiedTables) {\n this.dispatch(\"REMOVE_FILTER_TABLE\", {\n sheetId: this.getters.getActiveSheetId(),\n target: [filterTable.zone],\n });\n }\n this.pasteCopiedTables(target);\n }\n /**\n * The clipped zone is copied as many times as it fits in the target.\n * This returns the list of zones where the clipped zone is copy-pasted.\n */\n pastedZones(target, originWidth, originHeight) {\n const selection = target[0];\n const repeatHorizontally = Math.max(1, Math.floor((selection.right + 1 - selection.left) / originWidth));\n const repeatVertically = Math.max(1, Math.floor((selection.bottom + 1 - selection.top) / originHeight));\n const zones = [];\n for (let x = 0; x < repeatHorizontally; x++) {\n for (let y = 0; y < repeatVertically; y++) {\n const top = selection.top + y * originHeight;\n const left = selection.left + x * originWidth;\n zones.push({\n left,\n top,\n bottom: top + originHeight - 1,\n right: left + originWidth - 1,\n });\n }\n }\n return zones;\n }\n /**\n * Compute the complete zones where to paste the current clipboard\n */\n getPasteZones(target) {\n const cells = this.cells;\n if (!cells.length || !cells[0].length) {\n return target;\n }\n const pasteZones = [];\n const height = cells.length;\n const width = cells[0].length;\n const selection = target[target.length - 1];\n const col = selection.left;\n const row = selection.top;\n const repetitionCol = Math.max(1, Math.floor((selection.right + 1 - col) / width));\n const repetitionRow = Math.max(1, Math.floor((selection.bottom + 1 - row) / height));\n for (let x = 1; x <= repetitionCol; x++) {\n for (let y = 1; y <= repetitionRow; y++) {\n pasteZones.push({\n left: col,\n top: row,\n right: col - 1 + x * width,\n bottom: row - 1 + y * height,\n });\n }\n }\n return pasteZones;\n }\n /**\n * Update the selection with the newly pasted zone\n */\n selectPastedZone(width, height, isCutOperation, target) {\n const selection = target[0];\n const col = selection.left;\n const row = selection.top;\n if (height > 1 || width > 1 || isCutOperation) {\n const zones = this.pastedZones(target, width, height);\n const newZone = isCutOperation ? zones[0] : union(...zones);\n this.selection.selectZone({ cell: { col, row }, zone: newZone });\n }\n }\n /**\n * Clear the clipped zones: remove the cells and clear the formatting\n */\n clearClippedZones() {\n for (const row of this.cells) {\n for (const cell of row) {\n if (cell.cell) {\n this.dispatch(\"CLEAR_CELL\", cell.position);\n }\n }\n }\n this.dispatch(\"CLEAR_FORMATTING\", {\n sheetId: this.sheetId,\n target: this.zones,\n });\n }\n pasteZone(col, row, clipboardOptions) {\n const height = this.cells.length;\n const width = this.cells[0].length;\n // This condition is used to determine if we have to paste the CF or not.\n // We have to do it when the command handled is \"PASTE\", not \"INSERT_CELL\"\n // or \"DELETE_CELL\". So, the state should be the local state\n const shouldPasteCF = (clipboardOptions === null || clipboardOptions === void 0 ? void 0 : clipboardOptions.pasteOption) !== \"onlyValue\" && (clipboardOptions === null || clipboardOptions === void 0 ? void 0 : clipboardOptions.shouldPasteCF);\n const sheetId = this.getters.getActiveSheetId();\n // first, add missing cols/rows if needed\n this.addMissingDimensions(width, height, col, row);\n // then, perform the actual paste operation\n for (let r = 0; r < height; r++) {\n const rowCells = this.cells[r];\n for (let c = 0; c < width; c++) {\n const origin = rowCells[c];\n const position = { col: col + c, row: row + r, sheetId: sheetId };\n // TODO: refactor this part. the \"Paste merge\" action is also executed with\n // MOVE_RANGES in pasteFromCut. Adding a condition on the operation type here\n // is not appropriate\n if (this.operation !== \"CUT\") {\n this.pasteMergeIfExist(origin.position, position);\n }\n this.pasteCell(origin, position, this.operation, clipboardOptions);\n if (shouldPasteCF) {\n this.dispatch(\"PASTE_CONDITIONAL_FORMAT\", {\n origin: origin.position,\n target: position,\n operation: this.operation,\n });\n }\n }\n }\n }\n /**\n * Paste the cell at the given position to the target position\n */\n pasteCell(origin, target, operation, clipboardOption) {\n const { sheetId, col, row } = target;\n const targetCell = this.getters.getEvaluatedCell(target);\n if ((clipboardOption === null || clipboardOption === void 0 ? void 0 : clipboardOption.pasteOption) !== \"onlyValue\") {\n const targetBorders = this.getters.getCellBorder(target);\n const originBorders = origin.border;\n const border = {\n top: (targetBorders === null || targetBorders === void 0 ? void 0 : targetBorders.top) || (originBorders === null || originBorders === void 0 ? void 0 : originBorders.top),\n bottom: (targetBorders === null || targetBorders === void 0 ? void 0 : targetBorders.bottom) || (originBorders === null || originBorders === void 0 ? void 0 : originBorders.bottom),\n left: (targetBorders === null || targetBorders === void 0 ? void 0 : targetBorders.left) || (originBorders === null || originBorders === void 0 ? void 0 : originBorders.left),\n right: (targetBorders === null || targetBorders === void 0 ? void 0 : targetBorders.right) || (originBorders === null || originBorders === void 0 ? void 0 : originBorders.right),\n };\n this.dispatch(\"SET_BORDER\", { sheetId, col, row, border });\n }\n if (origin.cell) {\n if ((clipboardOption === null || clipboardOption === void 0 ? void 0 : clipboardOption.pasteOption) === \"onlyFormat\") {\n this.dispatch(\"UPDATE_CELL\", {\n ...target,\n style: origin.cell.style,\n format: origin.evaluatedCell.format,\n });\n return;\n }\n if ((clipboardOption === null || clipboardOption === void 0 ? void 0 : clipboardOption.pasteOption) === \"onlyValue\") {\n const content = formatValue(origin.evaluatedCell.value);\n this.dispatch(\"UPDATE_CELL\", { ...target, content });\n return;\n }\n let content = origin.cell.content;\n if (origin.cell.isFormula && operation === \"COPY\") {\n const offsetX = col - origin.position.col;\n const offsetY = row - origin.position.row;\n content = this.getUpdatedContent(sheetId, origin.cell, offsetX, offsetY, operation);\n }\n this.dispatch(\"UPDATE_CELL\", {\n ...target,\n content,\n style: origin.cell.style || null,\n format: origin.cell.format,\n });\n }\n else if (targetCell) {\n if ((clipboardOption === null || clipboardOption === void 0 ? void 0 : clipboardOption.pasteOption) === \"onlyValue\") {\n this.dispatch(\"UPDATE_CELL\", { ...target, content: \"\" });\n }\n else if ((clipboardOption === null || clipboardOption === void 0 ? void 0 : clipboardOption.pasteOption) === \"onlyFormat\") {\n this.dispatch(\"UPDATE_CELL\", { ...target, style: null, format: \"\" });\n }\n else {\n this.dispatch(\"CLEAR_CELL\", target);\n }\n }\n }\n /**\n * Get the newly updated formula, after applying offsets\n */\n getUpdatedContent(sheetId, cell, offsetX, offsetY, operation) {\n const ranges = this.getters.createAdaptedRanges(cell.dependencies, offsetX, offsetY, sheetId);\n return this.getters.buildFormulaContent(sheetId, cell, ranges);\n }\n /**\n * If the origin position given is the top left of a merge, merge the target\n * position.\n */\n pasteMergeIfExist(origin, target) {\n let { sheetId, col, row } = origin;\n const { col: mainCellColOrigin, row: mainCellRowOrigin } = this.getters.getMainCellPosition(origin);\n if (mainCellColOrigin === col && mainCellRowOrigin === row) {\n const merge = this.getters.getMerge(origin);\n if (!merge) {\n return;\n }\n ({ sheetId, col, row } = target);\n this.dispatch(\"ADD_MERGE\", {\n sheetId,\n force: true,\n target: [\n {\n left: col,\n top: row,\n right: col + merge.right - merge.left,\n bottom: row + merge.bottom - merge.top,\n },\n ],\n });\n }\n }\n /** Paste the filter tables that are in the state */\n pasteCopiedTables(target) {\n const sheetId = this.getters.getActiveSheetId();\n const selection = target[0];\n const cutZone = this.zones[0];\n const cutOffset = [\n selection.left - cutZone.left,\n selection.top - cutZone.top,\n ];\n for (const table of this.copiedTables) {\n const newTableZone = createAdaptedZone(table.zone, \"both\", \"MOVE\", cutOffset);\n this.dispatch(\"CREATE_FILTER_TABLE\", { sheetId, target: [newTableZone] });\n for (const i of range(0, table.filtersValues.length)) {\n this.dispatch(\"UPDATE_FILTER\", {\n sheetId,\n col: newTableZone.left + i,\n row: newTableZone.top,\n values: table.filtersValues[i],\n });\n }\n }\n }\n getClipboardContent() {\n return {\n [ClipboardMIMEType.PlainText]: this.getPlainTextContent(),\n [ClipboardMIMEType.Html]: this.getHTMLContent(),\n };\n }\n getPlainTextContent() {\n return (this.cells\n .map((cells) => {\n return cells\n .map((c) => c.cell ? this.getters.getCellText(c.position, this.getters.shouldShowFormulas()) : \"\")\n .join(\"\\t\");\n })\n .join(\"\\n\") || \"\\t\");\n }\n getHTMLContent() {\n if (this.cells.length == 1 && this.cells[0].length == 1) {\n return this.getters.getCellText(this.cells[0][0].position);\n }\n let htmlTable = '';\n for (const row of this.cells) {\n htmlTable += \"\";\n for (const cell of row) {\n const cssStyle = cssPropertiesToCss(cellStyleToCss(cell.style), false);\n const cellText = this.getters.getCellText(cell.position);\n htmlTable += `\";\n }\n htmlTable += \"\";\n }\n htmlTable += \"
` + xmlEscape(cellText) + \"
\";\n return htmlTable;\n }\n isColRowDirtyingClipboard(position, dimension) {\n if (!this.zones)\n return false;\n for (let zone of this.zones) {\n if (dimension === \"COL\" && position <= zone.right) {\n return true;\n }\n if (dimension === \"ROW\" && position <= zone.bottom) {\n return true;\n }\n }\n return false;\n }\n drawClipboard(renderingContext) {\n const { ctx, thinLineWidth } = renderingContext;\n if (this.sheetId !== this.getters.getActiveSheetId() || !this.zones || !this.zones.length) {\n return;\n }\n ctx.setLineDash([8, 5]);\n ctx.strokeStyle = SELECTION_BORDER_COLOR;\n ctx.lineWidth = 3.3 * thinLineWidth;\n for (const zone of this.zones) {\n const { x, y, width, height } = this.getters.getVisibleRect(zone);\n if (width > 0 && height > 0) {\n ctx.strokeRect(x, y, width, height);\n }\n }\n }\n }\n\n /** State of the clipboard when copying/cutting figures */\n class ClipboardFigureState {\n constructor(operation, getters, dispatch) {\n this.operation = operation;\n this.getters = getters;\n this.dispatch = dispatch;\n this.sheetId = getters.getActiveSheetId();\n const copiedFigureId = getters.getSelectedFigureId();\n if (!copiedFigureId) {\n throw new Error(`No figure selected`);\n }\n const figure = getters.getFigure(this.sheetId, copiedFigureId);\n if (!figure) {\n throw new Error(`No figure for the given id: ${copiedFigureId}`);\n }\n this.copiedFigure = { ...figure };\n switch (figure.tag) {\n case \"chart\":\n this.copiedFigureContent = new ClipboardFigureChart(dispatch, getters, this.sheetId, copiedFigureId);\n break;\n case \"image\":\n this.copiedFigureContent = new ClipboardFigureImage(dispatch, getters, this.sheetId, copiedFigureId);\n break;\n default:\n throw new Error(`Unknow tag '${figure.tag}' for the given figure id: ${copiedFigureId}`);\n }\n }\n isCutAllowed(target) {\n return 0 /* CommandResult.Success */;\n }\n isPasteAllowed(target, option) {\n if (target.length === 0) {\n return 73 /* CommandResult.EmptyTarget */;\n }\n if ((option === null || option === void 0 ? void 0 : option.pasteOption) !== undefined) {\n return 22 /* CommandResult.WrongFigurePasteOption */;\n }\n return 0 /* CommandResult.Success */;\n }\n /**\n * Paste the clipboard content in the given target\n */\n paste(target) {\n const sheetId = this.getters.getActiveSheetId();\n const position = {\n x: this.getters.getColDimensions(sheetId, target[0].left).start,\n y: this.getters.getRowDimensions(sheetId, target[0].top).start,\n };\n const size = { height: this.copiedFigure.height, width: this.copiedFigure.width };\n const newId = new UuidGenerator().uuidv4();\n this.copiedFigureContent.paste(sheetId, newId, position, size);\n if (this.operation === \"CUT\") {\n this.dispatch(\"DELETE_FIGURE\", {\n sheetId: this.copiedFigureContent.sheetId,\n id: this.copiedFigure.id,\n });\n }\n this.dispatch(\"SELECT_FIGURE\", { id: newId });\n }\n getClipboardContent() {\n return { [ClipboardMIMEType.PlainText]: \"\\t\" };\n }\n isColRowDirtyingClipboard(position, dimension) {\n return false;\n }\n drawClipboard(renderingContext) { }\n }\n class ClipboardFigureChart {\n constructor(dispatch, getters, sheetId, copiedFigureId) {\n this.dispatch = dispatch;\n this.sheetId = sheetId;\n const chart = getters.getChart(copiedFigureId);\n if (!chart) {\n throw new Error(`No chart for the given id: ${copiedFigureId}`);\n }\n this.copiedChart = chart.copyInSheetId(sheetId);\n }\n paste(sheetId, figureId, position, size) {\n const copy = this.copiedChart.copyInSheetId(sheetId);\n this.dispatch(\"CREATE_CHART\", {\n id: figureId,\n sheetId,\n position,\n size,\n definition: copy.getDefinition(),\n });\n }\n }\n class ClipboardFigureImage {\n constructor(dispatch, getters, sheetId, copiedFigureId) {\n this.dispatch = dispatch;\n this.sheetId = sheetId;\n const image = getters.getImage(copiedFigureId);\n this.copiedImage = deepCopy(image);\n }\n paste(sheetId, figureId, position, size) {\n const copy = deepCopy(this.copiedImage);\n this.dispatch(\"CREATE_IMAGE\", {\n figureId,\n sheetId,\n position,\n size,\n definition: copy,\n });\n }\n }\n\n /** State of the clipboard when copying/cutting from the OS clipboard*/\n class ClipboardOsState extends ClipboardCellsAbstractState {\n constructor(content, getters, dispatch, selection) {\n super(\"COPY\", getters, dispatch, selection);\n this.values = content\n .replace(/\\r/g, \"\")\n .split(\"\\n\")\n .map((vals) => vals.split(\"\\t\"));\n }\n isPasteAllowed(target, clipboardOption) {\n const sheetId = this.getters.getActiveSheetId();\n const pasteZone = this.getPasteZone(target);\n if (this.getters.doesIntersectMerge(sheetId, pasteZone)) {\n return 2 /* CommandResult.WillRemoveExistingMerge */;\n }\n return 0 /* CommandResult.Success */;\n }\n paste(target) {\n const values = this.values;\n const pasteZone = this.getPasteZone(target);\n const { left: activeCol, top: activeRow } = pasteZone;\n const { width, height } = zoneToDimension(pasteZone);\n const sheetId = this.getters.getActiveSheetId();\n this.addMissingDimensions(width, height, activeCol, activeRow);\n for (let i = 0; i < values.length; i++) {\n for (let j = 0; j < values[i].length; j++) {\n this.dispatch(\"UPDATE_CELL\", {\n row: activeRow + i,\n col: activeCol + j,\n content: values[i][j],\n sheetId,\n });\n }\n }\n const zone = {\n left: activeCol,\n top: activeRow,\n right: activeCol + width - 1,\n bottom: activeRow + height - 1,\n };\n this.selection.selectZone({ cell: { col: activeCol, row: activeRow }, zone });\n }\n getClipboardContent() {\n return {\n [ClipboardMIMEType.PlainText]: this.values.map((values) => values.join(\"\\t\")).join(\"\\n\"),\n };\n }\n getPasteZone(target) {\n const height = this.values.length;\n const width = Math.max(...this.values.map((a) => a.length));\n const { left: activeCol, top: activeRow } = target[0];\n return {\n top: activeRow,\n left: activeCol,\n bottom: activeRow + height - 1,\n right: activeCol + width - 1,\n };\n }\n }\n\n /**\n * Clipboard Plugin\n *\n * This clipboard manages all cut/copy/paste interactions internal to the\n * application, and with the OS clipboard as well.\n */\n class ClipboardPlugin extends UIPlugin {\n constructor() {\n super(...arguments);\n this.status = \"invisible\";\n this._isPaintingFormat = false;\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"CUT\":\n const zones = cmd.target || this.getters.getSelectedZones();\n const state = this.getClipboardState(zones, cmd.type);\n return state.isCutAllowed(zones);\n case \"PASTE\":\n if (!this.state) {\n return 23 /* CommandResult.EmptyClipboard */;\n }\n const pasteOption = cmd.pasteOption || (this._isPaintingFormat ? \"onlyFormat\" : undefined);\n return this.state.isPasteAllowed(cmd.target, { pasteOption });\n case \"PASTE_FROM_OS_CLIPBOARD\": {\n const state = new ClipboardOsState(cmd.text, this.getters, this.dispatch, this.selection);\n return state.isPasteAllowed(cmd.target);\n }\n case \"INSERT_CELL\": {\n const { cut, paste } = this.getInsertCellsTargets(cmd.zone, cmd.shiftDimension);\n const state = this.getClipboardStateForCopyCells(cut, \"CUT\");\n return state.isPasteAllowed(paste);\n }\n case \"DELETE_CELL\": {\n const { cut, paste } = this.getDeleteCellsTargets(cmd.zone, cmd.shiftDimension);\n const state = this.getClipboardStateForCopyCells(cut, \"CUT\");\n return state.isPasteAllowed(paste);\n }\n }\n return 0 /* CommandResult.Success */;\n }\n handle(cmd) {\n var _a, _b;\n switch (cmd.type) {\n case \"COPY\":\n case \"CUT\":\n const zones = (\"target\" in cmd && cmd.target) || this.getters.getSelectedZones();\n this.state = this.getClipboardState(zones, cmd.type);\n this.status = \"visible\";\n break;\n case \"PASTE\":\n if (!this.state) {\n break;\n }\n const pasteOption = cmd.pasteOption || (this._isPaintingFormat ? \"onlyFormat\" : undefined);\n this._isPaintingFormat = false;\n this.state.paste(cmd.target, { pasteOption, shouldPasteCF: true, selectTarget: true });\n if (this.state.operation === \"CUT\") {\n this.state = undefined;\n }\n this.status = \"invisible\";\n break;\n case \"CLEAN_CLIPBOARD_HIGHLIGHT\":\n this.status = \"invisible\";\n break;\n case \"DELETE_CELL\": {\n const { cut, paste } = this.getDeleteCellsTargets(cmd.zone, cmd.shiftDimension);\n if (!isZoneValid(cut[0])) {\n for (const { col, row } of positions(cmd.zone)) {\n this.dispatch(\"CLEAR_CELL\", { col, row, sheetId: this.getters.getActiveSheetId() });\n }\n break;\n }\n const state = this.getClipboardStateForCopyCells(cut, \"CUT\");\n state.paste(paste);\n break;\n }\n case \"INSERT_CELL\": {\n const { cut, paste } = this.getInsertCellsTargets(cmd.zone, cmd.shiftDimension);\n const state = this.getClipboardStateForCopyCells(cut, \"CUT\");\n state.paste(paste);\n break;\n }\n case \"ADD_COLUMNS_ROWS\": {\n this.status = \"invisible\";\n // If we add a col/row inside or before the cut area, we invalidate the clipboard\n if (((_a = this.state) === null || _a === void 0 ? void 0 : _a.operation) !== \"CUT\") {\n return;\n }\n const isClipboardDirty = this.state.isColRowDirtyingClipboard(cmd.position === \"before\" ? cmd.base : cmd.base + 1, cmd.dimension);\n if (isClipboardDirty) {\n this.state = undefined;\n }\n break;\n }\n case \"REMOVE_COLUMNS_ROWS\": {\n this.status = \"invisible\";\n // If we remove a col/row inside or before the cut area, we invalidate the clipboard\n if (((_b = this.state) === null || _b === void 0 ? void 0 : _b.operation) !== \"CUT\") {\n return;\n }\n for (let el of cmd.elements) {\n const isClipboardDirty = this.state.isColRowDirtyingClipboard(el, cmd.dimension);\n if (isClipboardDirty) {\n this.state = undefined;\n break;\n }\n }\n this.status = \"invisible\";\n break;\n }\n case \"PASTE_FROM_OS_CLIPBOARD\":\n const state = new ClipboardOsState(cmd.text, this.getters, this.dispatch, this.selection);\n state.paste(cmd.target);\n this.status = \"invisible\";\n break;\n case \"ACTIVATE_PAINT_FORMAT\": {\n const zones = this.getters.getSelectedZones();\n this.state = this.getClipboardStateForCopyCells(zones, \"COPY\");\n this._isPaintingFormat = true;\n this.status = \"visible\";\n break;\n }\n default:\n if (isCoreCommand(cmd)) {\n this.status = \"invisible\";\n }\n }\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n /**\n * Format the current clipboard to a string suitable for being pasted in other\n * programs.\n *\n * - add a tab character between each consecutive cells\n * - add a newline character between each line\n *\n * Note that it returns \\t if the clipboard is empty. This is necessary for the\n * clipboard copy event to add it as data, otherwise an empty string is not\n * considered as a copy content.\n */\n getClipboardContent() {\n var _a;\n return ((_a = this.state) === null || _a === void 0 ? void 0 : _a.getClipboardContent()) || { [ClipboardMIMEType.PlainText]: \"\\t\" };\n }\n getClipboardTextContent() {\n var _a;\n return ((_a = this.state) === null || _a === void 0 ? void 0 : _a.getClipboardContent()[ClipboardMIMEType.PlainText]) || \"\\t\";\n }\n isCutOperation() {\n return this.state ? this.state.operation === \"CUT\" : false;\n }\n isPaintingFormat() {\n return this._isPaintingFormat;\n }\n // ---------------------------------------------------------------------------\n // Private methods\n // ---------------------------------------------------------------------------\n getDeleteCellsTargets(zone, dimension) {\n const sheetId = this.getters.getActiveSheetId();\n let cut;\n if (dimension === \"COL\") {\n cut = {\n ...zone,\n left: zone.right + 1,\n right: this.getters.getNumberCols(sheetId) - 1,\n };\n }\n else {\n cut = {\n ...zone,\n top: zone.bottom + 1,\n bottom: this.getters.getNumberRows(sheetId) - 1,\n };\n }\n return { cut: [cut], paste: [zone] };\n }\n getInsertCellsTargets(zone, dimension) {\n const sheetId = this.getters.getActiveSheetId();\n let cut;\n let paste;\n if (dimension === \"COL\") {\n cut = {\n ...zone,\n right: this.getters.getNumberCols(sheetId) - 1,\n };\n paste = {\n ...zone,\n left: zone.right + 1,\n right: zone.right + 1,\n };\n }\n else {\n cut = {\n ...zone,\n bottom: this.getters.getNumberRows(sheetId) - 1,\n };\n paste = { ...zone, top: zone.bottom + 1, bottom: this.getters.getNumberRows(sheetId) - 1 };\n }\n return { cut: [cut], paste: [paste] };\n }\n getClipboardStateForCopyCells(zones, operation) {\n return new ClipboardCellsState(zones, operation, this.getters, this.dispatch, this.selection);\n }\n /**\n * Get the clipboard state from the given zones.\n */\n getClipboardState(zones, operation) {\n const selectedFigureId = this.getters.getSelectedFigureId();\n if (selectedFigureId) {\n return new ClipboardFigureState(operation, this.getters, this.dispatch);\n }\n return new ClipboardCellsState(zones, operation, this.getters, this.dispatch, this.selection);\n }\n // ---------------------------------------------------------------------------\n // Grid rendering\n // ---------------------------------------------------------------------------\n drawGrid(renderingContext) {\n if (this.status !== \"visible\" || !this.state) {\n return;\n }\n this.state.drawClipboard(renderingContext);\n }\n }\n ClipboardPlugin.layers = [2 /* LAYERS.Clipboard */];\n ClipboardPlugin.getters = [\n \"getClipboardContent\",\n \"getClipboardTextContent\",\n \"isCutOperation\",\n \"isPaintingFormat\",\n ];\n\n const selectionStatisticFunctions = [\n {\n name: _lt(\"Sum\"),\n types: [CellValueType.number],\n compute: (values) => SUM.compute([values]),\n },\n {\n name: _lt(\"Avg\"),\n types: [CellValueType.number],\n compute: (values) => AVERAGE.compute([values]),\n },\n {\n name: _lt(\"Min\"),\n types: [CellValueType.number],\n compute: (values) => MIN.compute([values]),\n },\n {\n name: _lt(\"Max\"),\n types: [CellValueType.number],\n compute: (values) => MAX.compute([values]),\n },\n {\n name: _lt(\"Count\"),\n types: [CellValueType.number, CellValueType.text, CellValueType.boolean, CellValueType.error],\n compute: (values) => COUNTA.compute([values]),\n },\n {\n name: _lt(\"Count Numbers\"),\n types: [CellValueType.number, CellValueType.text, CellValueType.boolean, CellValueType.error],\n compute: (values) => COUNT.compute([values]),\n },\n ];\n /**\n * SelectionPlugin\n */\n class GridSelectionPlugin extends UIPlugin {\n constructor(config) {\n super(config);\n this.gridSelection = {\n anchor: {\n cell: { col: 0, row: 0 },\n zone: { top: 0, left: 0, bottom: 0, right: 0 },\n },\n zones: [{ top: 0, left: 0, bottom: 0, right: 0 }],\n };\n this.selectedFigureId = null;\n this.sheetsData = {};\n // This flag is used to avoid to historize the ACTIVE_SHEET command when it's\n // the main command.\n this.activeSheet = null;\n this.moveClient = config.moveClient;\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"ACTIVATE_SHEET\":\n try {\n this.getters.getSheet(cmd.sheetIdTo);\n break;\n }\n catch (error) {\n return 27 /* CommandResult.InvalidSheetId */;\n }\n case \"MOVE_COLUMNS_ROWS\":\n return this.isMoveElementAllowed(cmd);\n }\n return 0 /* CommandResult.Success */;\n }\n handleEvent(event) {\n const anchor = event.anchor;\n let zones = [];\n switch (event.mode) {\n case \"overrideSelection\":\n zones = [anchor.zone];\n break;\n case \"updateAnchor\":\n zones = [...this.gridSelection.zones];\n const index = zones.findIndex((z) => isEqual(z, event.previousAnchor.zone));\n if (index >= 0) {\n zones[index] = anchor.zone;\n }\n break;\n case \"newAnchor\":\n zones = [...this.gridSelection.zones, anchor.zone];\n break;\n }\n this.setSelectionMixin(event.anchor, zones);\n /** Any change to the selection has to be reflected in the selection processor. */\n this.selection.resetDefaultAnchor(this, deepCopy(this.gridSelection.anchor));\n const { col, row } = this.gridSelection.anchor.cell;\n this.moveClient({\n sheetId: this.getters.getActiveSheetId(),\n col,\n row,\n });\n this.selectedFigureId = null;\n }\n handle(cmd) {\n switch (cmd.type) {\n case \"START_EDITION\":\n case \"ACTIVATE_SHEET\":\n this.selectedFigureId = null;\n break;\n case \"DELETE_FIGURE\":\n if (this.selectedFigureId === cmd.id) {\n this.selectedFigureId = null;\n }\n break;\n case \"DELETE_SHEET\":\n if (this.selectedFigureId && this.getters.getFigure(cmd.sheetId, this.selectedFigureId)) {\n this.selectedFigureId = null;\n }\n break;\n }\n switch (cmd.type) {\n case \"START\":\n const firstSheetId = this.getters.getVisibleSheetIds()[0];\n this.dispatch(\"ACTIVATE_SHEET\", {\n sheetIdTo: firstSheetId,\n sheetIdFrom: firstSheetId,\n });\n const { col, row } = this.getters.getNextVisibleCellPosition({\n sheetId: firstSheetId,\n col: 0,\n row: 0,\n });\n this.selectCell(col, row);\n this.selection.registerAsDefault(this, this.gridSelection.anchor, {\n handleEvent: this.handleEvent.bind(this),\n });\n this.moveClient({ sheetId: firstSheetId, col: 0, row: 0 });\n break;\n case \"ACTIVATE_SHEET\": {\n if (!this.getters.isSheetVisible(cmd.sheetIdTo)) {\n this.dispatch(\"SHOW_SHEET\", { sheetId: cmd.sheetIdTo });\n }\n this.setActiveSheet(cmd.sheetIdTo);\n this.sheetsData[cmd.sheetIdFrom] = {\n gridSelection: deepCopy(this.gridSelection),\n };\n if (cmd.sheetIdTo in this.sheetsData) {\n Object.assign(this, this.sheetsData[cmd.sheetIdTo]);\n this.selection.resetDefaultAnchor(this, deepCopy(this.gridSelection.anchor));\n }\n else {\n const { col, row } = this.getters.getNextVisibleCellPosition({\n sheetId: cmd.sheetIdTo,\n col: 0,\n row: 0,\n });\n this.selectCell(col, row);\n }\n break;\n }\n case \"REMOVE_COLUMNS_ROWS\": {\n const sheetId = this.getters.getActiveSheetId();\n if (cmd.sheetId === sheetId) {\n if (cmd.dimension === \"COL\") {\n this.onColumnsRemoved(cmd);\n }\n else {\n this.onRowsRemoved(cmd);\n }\n const { col, row } = this.gridSelection.anchor.cell;\n this.moveClient({ sheetId, col, row });\n }\n break;\n }\n case \"ADD_COLUMNS_ROWS\": {\n const sheetId = this.getters.getActiveSheetId();\n if (cmd.sheetId === sheetId) {\n this.onAddElements(cmd);\n const { col, row } = this.gridSelection.anchor.cell;\n this.moveClient({ sheetId, col, row });\n }\n break;\n }\n case \"MOVE_COLUMNS_ROWS\":\n if (cmd.sheetId === this.getActiveSheetId()) {\n this.onMoveElements(cmd);\n }\n break;\n case \"SELECT_FIGURE\":\n this.selectedFigureId = cmd.id;\n break;\n case \"ACTIVATE_NEXT_SHEET\":\n this.activateNextSheet(\"right\");\n break;\n case \"ACTIVATE_PREVIOUS_SHEET\":\n this.activateNextSheet(\"left\");\n break;\n case \"HIDE_SHEET\":\n if (cmd.sheetId === this.getActiveSheetId()) {\n this.dispatch(\"ACTIVATE_SHEET\", {\n sheetIdFrom: cmd.sheetId,\n sheetIdTo: this.getters.getVisibleSheetIds()[0],\n });\n }\n break;\n case \"UNDO\":\n case \"REDO\":\n case \"DELETE_SHEET\":\n const deletedSheetIds = Object.keys(this.sheetsData).filter((sheetId) => !this.getters.tryGetSheet(sheetId));\n for (const sheetId of deletedSheetIds) {\n delete this.sheetsData[sheetId];\n }\n for (const sheetId in this.sheetsData) {\n const gridSelection = this.clipSelection(sheetId, this.sheetsData[sheetId].gridSelection);\n this.sheetsData[sheetId] = {\n gridSelection: deepCopy(gridSelection),\n };\n }\n if (!this.getters.tryGetSheet(this.getters.getActiveSheetId())) {\n const currentSheetIds = this.getters.getVisibleSheetIds();\n this.activeSheet = this.getters.getSheet(currentSheetIds[0]);\n if (this.activeSheet.id in this.sheetsData) {\n const { anchor } = this.clipSelection(this.activeSheet.id, this.sheetsData[this.activeSheet.id].gridSelection);\n this.selectCell(anchor.cell.col, anchor.cell.row);\n }\n else {\n this.selectCell(0, 0);\n }\n const { col, row } = this.gridSelection.anchor.cell;\n this.moveClient({\n sheetId: this.getters.getActiveSheetId(),\n col,\n row,\n });\n }\n const sheetId = this.getters.getActiveSheetId();\n this.gridSelection.zones = this.gridSelection.zones.map((z) => this.getters.expandZone(sheetId, z));\n this.gridSelection.anchor.zone = this.getters.expandZone(sheetId, this.gridSelection.anchor.zone);\n this.setSelectionMixin(this.gridSelection.anchor, this.gridSelection.zones);\n break;\n }\n /** Any change to the selection has to be reflected in the selection processor. */\n this.selection.resetDefaultAnchor(this, deepCopy(this.gridSelection.anchor));\n }\n // ---------------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------------\n getActiveSheet() {\n return this.activeSheet;\n }\n getActiveSheetId() {\n return this.activeSheet.id;\n }\n getActiveCell() {\n return this.getters.getEvaluatedCell(this.getActivePosition());\n }\n getActiveCols() {\n const activeCols = new Set();\n for (let zone of this.gridSelection.zones) {\n if (zone.top === 0 &&\n zone.bottom === this.getters.getNumberRows(this.getters.getActiveSheetId()) - 1) {\n for (let i = zone.left; i <= zone.right; i++) {\n activeCols.add(i);\n }\n }\n }\n return activeCols;\n }\n getActiveRows() {\n const activeRows = new Set();\n const sheetId = this.getters.getActiveSheetId();\n for (let zone of this.gridSelection.zones) {\n if (zone.left === 0 && zone.right === this.getters.getNumberCols(sheetId) - 1) {\n for (let i = zone.top; i <= zone.bottom; i++) {\n activeRows.add(i);\n }\n }\n }\n return activeRows;\n }\n getCurrentStyle() {\n const zone = this.getters.getSelectedZone();\n const sheetId = this.getters.getActiveSheetId();\n return this.getters.getCellStyle({ sheetId, col: zone.left, row: zone.top });\n }\n getSelectedZones() {\n return deepCopy(this.gridSelection.zones);\n }\n getSelectedZone() {\n return deepCopy(this.gridSelection.anchor.zone);\n }\n getSelection() {\n return deepCopy(this.gridSelection);\n }\n getSelectedFigureId() {\n return this.selectedFigureId;\n }\n getActivePosition() {\n return this.getters.getMainCellPosition({\n sheetId: this.getActiveSheetId(),\n col: this.gridSelection.anchor.cell.col,\n row: this.gridSelection.anchor.cell.row,\n });\n }\n getSheetPosition(sheetId) {\n if (sheetId === this.getters.getActiveSheetId()) {\n return this.getActivePosition();\n }\n else {\n const sheetData = this.sheetsData[sheetId];\n return sheetData\n ? {\n sheetId,\n col: sheetData.gridSelection.anchor.cell.col,\n row: sheetData.gridSelection.anchor.cell.row,\n }\n : this.getters.getNextVisibleCellPosition({ sheetId, col: 0, row: 0 });\n }\n }\n getStatisticFnResults() {\n // get deduplicated cells in zones\n const cells = new Set(this.gridSelection.zones\n .map((zone) => this.getters.getEvaluatedCellsInZone(this.getters.getActiveSheetId(), zone))\n .flat()\n .filter((cell) => cell.type !== CellValueType.empty));\n let cellsTypes = new Set();\n let cellsValues = [];\n for (let cell of cells) {\n cellsTypes.add(cell.type);\n cellsValues.push(cell.value);\n }\n let statisticFnResults = {};\n for (let fn of selectionStatisticFunctions) {\n // We don't want to display statistical information when there is no interest:\n // We set the statistical result to undefined if the data handled by the selection\n // does not match the data handled by the function.\n // Ex: if there are only texts in the selection, we prefer that the SUM result\n // be displayed as undefined rather than 0.\n let fnResult = undefined;\n if (fn.types.some((t) => cellsTypes.has(t))) {\n fnResult = fn.compute(cellsValues);\n }\n statisticFnResults[fn.name] = fnResult;\n }\n return statisticFnResults;\n }\n getAggregate() {\n let aggregate = 0;\n let n = 0;\n const sheetId = this.getters.getActiveSheetId();\n const cellPositions = this.gridSelection.zones.map(positions).flat();\n for (const { col, row } of cellPositions) {\n const cell = this.getters.getEvaluatedCell({ sheetId, col, row });\n if (cell.type === CellValueType.number) {\n n++;\n aggregate += cell.value;\n }\n }\n return n < 2 ? null : formatValue(aggregate);\n }\n isSelected(zone) {\n return !!this.getters.getSelectedZones().find((z) => isEqual(z, zone));\n }\n /**\n * Returns a sorted array of indexes of all columns (respectively rows depending\n * on the dimension parameter) intersected by the currently selected zones.\n *\n * example:\n * assume selectedZones: [{left:0, right: 2, top :2, bottom: 4}, {left:5, right: 6, top :3, bottom: 5}]\n *\n * if dimension === \"COL\" => [0,1,2,5,6]\n * if dimension === \"ROW\" => [2,3,4,5]\n */\n getElementsFromSelection(dimension) {\n if (dimension === \"COL\" && this.getters.getActiveCols().size === 0) {\n return [];\n }\n if (dimension === \"ROW\" && this.getters.getActiveRows().size === 0) {\n return [];\n }\n const zones = this.getters.getSelectedZones();\n let elements = [];\n const start = dimension === \"COL\" ? \"left\" : \"top\";\n const end = dimension === \"COL\" ? \"right\" : \"bottom\";\n for (const zone of zones) {\n const zoneRows = Array.from({ length: zone[end] - zone[start] + 1 }, (_, i) => zone[start] + i);\n elements = elements.concat(zoneRows);\n }\n return [...new Set(elements)].sort();\n }\n // ---------------------------------------------------------------------------\n // Other\n // ---------------------------------------------------------------------------\n /**\n * Ensure selections are not outside sheet boundaries.\n * They are clipped to fit inside the sheet if needed.\n */\n setSelectionMixin(anchor, zones) {\n const { anchor: clippedAnchor, zones: clippedZones } = this.clipSelection(this.getters.getActiveSheetId(), { anchor, zones });\n this.gridSelection.anchor = clippedAnchor;\n this.gridSelection.zones = uniqueZones(clippedZones);\n }\n /**\n * Change the anchor of the selection active cell to an absolute col and row index.\n *\n * This is a non trivial task. We need to stop the editing process and update\n * properly the current selection. Also, this method can optionally create a new\n * range in the selection.\n */\n selectCell(col, row) {\n const sheetId = this.getters.getActiveSheetId();\n const zone = this.getters.expandZone(sheetId, { left: col, right: col, top: row, bottom: row });\n this.setSelectionMixin({ zone, cell: { col, row } }, [zone]);\n }\n setActiveSheet(id) {\n const sheet = this.getters.getSheet(id);\n this.activeSheet = sheet;\n }\n activateNextSheet(direction) {\n const sheetIds = this.getters.getSheetIds();\n const oldSheetPosition = sheetIds.findIndex((id) => id === this.activeSheet.id);\n const delta = direction === \"left\" ? sheetIds.length - 1 : 1;\n const newPosition = (oldSheetPosition + delta) % sheetIds.length;\n this.dispatch(\"ACTIVATE_SHEET\", {\n sheetIdFrom: this.getActiveSheetId(),\n sheetIdTo: sheetIds[newPosition],\n });\n }\n onColumnsRemoved(cmd) {\n const { cell, zone } = this.gridSelection.anchor;\n const selectedZone = updateSelectionOnDeletion(zone, \"left\", [...cmd.elements]);\n let anchorZone = { left: cell.col, right: cell.col, top: cell.row, bottom: cell.row };\n anchorZone = updateSelectionOnDeletion(anchorZone, \"left\", [...cmd.elements]);\n const anchor = {\n cell: {\n col: anchorZone.left,\n row: anchorZone.top,\n },\n zone: selectedZone,\n };\n this.setSelectionMixin(anchor, [selectedZone]);\n }\n onRowsRemoved(cmd) {\n const { cell, zone } = this.gridSelection.anchor;\n const selectedZone = updateSelectionOnDeletion(zone, \"top\", [...cmd.elements]);\n let anchorZone = { left: cell.col, right: cell.col, top: cell.row, bottom: cell.row };\n anchorZone = updateSelectionOnDeletion(anchorZone, \"top\", [...cmd.elements]);\n const anchor = {\n cell: {\n col: anchorZone.left,\n row: anchorZone.top,\n },\n zone: selectedZone,\n };\n this.setSelectionMixin(anchor, [selectedZone]);\n }\n onAddElements(cmd) {\n const selection = this.gridSelection.anchor.zone;\n const zone = updateSelectionOnInsertion(selection, cmd.dimension === \"COL\" ? \"left\" : \"top\", cmd.base, cmd.position, cmd.quantity);\n const anchor = { cell: { col: zone.left, row: zone.top }, zone };\n this.setSelectionMixin(anchor, [zone]);\n }\n onMoveElements(cmd) {\n const thickness = cmd.elements.length;\n this.dispatch(\"ADD_COLUMNS_ROWS\", {\n dimension: cmd.dimension,\n sheetId: cmd.sheetId,\n base: cmd.base,\n quantity: thickness,\n position: \"before\",\n });\n const isCol = cmd.dimension === \"COL\";\n const start = cmd.elements[0];\n const end = cmd.elements[thickness - 1];\n const isBasedBefore = cmd.base < start;\n const deltaCol = isBasedBefore && isCol ? thickness : 0;\n const deltaRow = isBasedBefore && !isCol ? thickness : 0;\n this.dispatch(\"CUT\", {\n target: [\n {\n left: isCol ? start + deltaCol : 0,\n right: isCol ? end + deltaCol : this.getters.getNumberCols(cmd.sheetId) - 1,\n top: !isCol ? start + deltaRow : 0,\n bottom: !isCol ? end + deltaRow : this.getters.getNumberRows(cmd.sheetId) - 1,\n },\n ],\n });\n this.dispatch(\"PASTE\", {\n target: [\n {\n left: isCol ? cmd.base : 0,\n right: isCol ? cmd.base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,\n top: !isCol ? cmd.base : 0,\n bottom: !isCol ? cmd.base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,\n },\n ],\n });\n const toRemove = isBasedBefore ? cmd.elements.map((el) => el + thickness) : cmd.elements;\n let currentIndex = cmd.base;\n for (const element of toRemove) {\n const size = cmd.dimension === \"COL\"\n ? this.getters.getColSize(cmd.sheetId, element)\n : this.getters.getRowSize(cmd.sheetId, element);\n this.dispatch(\"RESIZE_COLUMNS_ROWS\", {\n dimension: cmd.dimension,\n sheetId: cmd.sheetId,\n size,\n elements: [currentIndex],\n });\n currentIndex += 1;\n }\n this.dispatch(\"REMOVE_COLUMNS_ROWS\", {\n dimension: cmd.dimension,\n sheetId: cmd.sheetId,\n elements: toRemove,\n });\n }\n isMoveElementAllowed(cmd) {\n const isCol = cmd.dimension === \"COL\";\n const start = cmd.elements[0];\n const end = cmd.elements[cmd.elements.length - 1];\n const id = cmd.sheetId;\n const doesElementsHaveCommonMerges = isCol\n ? this.getters.doesColumnsHaveCommonMerges\n : this.getters.doesRowsHaveCommonMerges;\n if (doesElementsHaveCommonMerges(id, start - 1, start) ||\n doesElementsHaveCommonMerges(id, end, end + 1) ||\n doesElementsHaveCommonMerges(id, cmd.base - 1, cmd.base)) {\n return 2 /* CommandResult.WillRemoveExistingMerge */;\n }\n return 0 /* CommandResult.Success */;\n }\n //-------------------------------------------\n // Helpers for extensions\n // ------------------------------------------\n /**\n * Clip the selection if it spans outside the sheet\n */\n clipSelection(sheetId, selection) {\n const cols = this.getters.getNumberCols(sheetId) - 1;\n const rows = this.getters.getNumberRows(sheetId) - 1;\n const zones = selection.zones.map((z) => {\n return {\n left: clip(z.left, 0, cols),\n right: clip(z.right, 0, cols),\n top: clip(z.top, 0, rows),\n bottom: clip(z.bottom, 0, rows),\n };\n });\n const anchorCol = clip(selection.anchor.cell.col, 0, cols);\n const anchorRow = clip(selection.anchor.cell.row, 0, rows);\n const anchorZone = {\n left: clip(selection.anchor.zone.left, 0, cols),\n right: clip(selection.anchor.zone.right, 0, cols),\n top: clip(selection.anchor.zone.top, 0, rows),\n bottom: clip(selection.anchor.zone.bottom, 0, rows),\n };\n return {\n zones,\n anchor: {\n cell: { col: anchorCol, row: anchorRow },\n zone: anchorZone,\n },\n };\n }\n // ---------------------------------------------------------------------------\n // Grid rendering\n // ---------------------------------------------------------------------------\n drawGrid(renderingContext) {\n if (this.getters.isDashboard()) {\n return;\n }\n const { ctx, thinLineWidth } = renderingContext;\n // selection\n const zones = this.getSelectedZones();\n ctx.fillStyle = \"#f3f7fe\";\n const onlyOneCell = zones.length === 1 && zones[0].left === zones[0].right && zones[0].top === zones[0].bottom;\n ctx.fillStyle = onlyOneCell ? \"#f3f7fe\" : \"#e9f0ff\";\n ctx.strokeStyle = SELECTION_BORDER_COLOR;\n ctx.lineWidth = 1.5 * thinLineWidth;\n for (const zone of zones) {\n const { x, y, width, height } = this.getters.getVisibleRect(zone);\n ctx.globalCompositeOperation = \"multiply\";\n ctx.fillRect(x, y, width, height);\n ctx.globalCompositeOperation = \"source-over\";\n ctx.strokeRect(x, y, width, height);\n }\n ctx.globalCompositeOperation = \"source-over\";\n // active zone\n const position = this.getActivePosition();\n ctx.strokeStyle = SELECTION_BORDER_COLOR;\n ctx.lineWidth = 3 * thinLineWidth;\n let zone;\n if (this.getters.isInMerge(position)) {\n zone = this.getters.getMerge(position);\n }\n else {\n zone = positionToZone(position);\n }\n const { x, y, width, height } = this.getters.getVisibleRect(zone);\n if (width > 0 && height > 0) {\n ctx.strokeRect(x, y, width, height);\n }\n }\n }\n GridSelectionPlugin.layers = [6 /* LAYERS.Selection */];\n GridSelectionPlugin.getters = [\n \"getActiveSheet\",\n \"getActiveSheetId\",\n \"getActiveCell\",\n \"getActiveCols\",\n \"getActiveRows\",\n \"getCurrentStyle\",\n \"getSelectedZones\",\n \"getSelectedZone\",\n \"getStatisticFnResults\",\n \"getAggregate\",\n \"getSelectedFigureId\",\n \"getSelection\",\n \"getActivePosition\",\n \"getSheetPosition\",\n \"isSelected\",\n \"getElementsFromSelection\",\n ];\n\n const corePluginRegistry = new Registry()\n .add(\"sheet\", SheetPlugin)\n .add(\"header visibility\", HeaderVisibilityPlugin)\n .add(\"filters\", FiltersPlugin)\n .add(\"cell\", CellPlugin)\n .add(\"merge\", MergePlugin)\n .add(\"headerSize\", HeaderSizePlugin)\n .add(\"borders\", BordersPlugin)\n .add(\"conditional formatting\", ConditionalFormatPlugin)\n .add(\"figures\", FigurePlugin)\n .add(\"chart\", ChartPlugin)\n .add(\"image\", ImagePlugin);\n // Plugins which handle a specific feature, without handling any core commands\n const featurePluginRegistry = new Registry()\n .add(\"ui_sheet\", SheetUIPlugin)\n .add(\"header_visibility_ui\", HeaderVisibilityUIPlugin)\n .add(\"ui_options\", UIOptionsPlugin)\n .add(\"selectionInputManager\", SelectionInputsManagerPlugin)\n .add(\"highlight\", HighlightPlugin)\n .add(\"grid renderer\", RendererPlugin)\n .add(\"autofill\", AutofillPlugin)\n .add(\"find_and_replace\", FindAndReplacePlugin)\n .add(\"sort\", SortPlugin)\n .add(\"automatic_sum\", AutomaticSumPlugin)\n .add(\"format\", FormatPlugin)\n .add(\"cell_popovers\", CellPopoverPlugin)\n .add(\"selection_multiuser\", SelectionMultiUserPlugin);\n // Plugins which have a state, but which should not be shared in collaborative\n const statefulUIPluginRegistry = new Registry()\n .add(\"selection\", GridSelectionPlugin)\n .add(\"clipboard\", ClipboardPlugin)\n .add(\"edition\", EditionPlugin);\n // Plugins which have a derived state from core data\n const coreViewsPluginRegistry = new Registry()\n .add(\"evaluation\", EvaluationPlugin)\n .add(\"evaluation_filter\", FilterEvaluationPlugin)\n .add(\"evaluation_chart\", EvaluationChartPlugin)\n .add(\"evaluation_cf\", EvaluationConditionalFormatPlugin)\n .add(\"viewport\", SheetViewPlugin)\n .add(\"custom_colors\", CustomColorsPlugin);\n\n const clickableCellRegistry = new Registry();\n clickableCellRegistry.add(\"link\", {\n condition: (position, env) => !!env.model.getters.getEvaluatedCell(position).link,\n action: (position, env) => openLink(env.model.getters.getEvaluatedCell(position).link, env),\n sequence: 5,\n });\n\n class ImageProvider {\n constructor(fileStore) {\n this.fileStore = fileStore;\n }\n async requestImage() {\n const file = await this.getImageFromUser();\n const path = await this.fileStore.upload(file);\n const size = await this.getImageSize(path);\n return { path, size };\n }\n getImageFromUser() {\n return new Promise((resolve, reject) => {\n const input = document.createElement(\"input\");\n input.setAttribute(\"type\", \"file\");\n input.setAttribute(\"accept\", \"image/*\");\n input.addEventListener(\"change\", async () => {\n if (input.files === null || input.files.length != 1) {\n reject();\n }\n else {\n resolve(input.files[0]);\n }\n });\n input.click();\n });\n }\n getImageSize(path) {\n return new Promise((resolve, reject) => {\n const image = new Image();\n image.src = path;\n image.addEventListener(\"load\", () => {\n const size = { width: image.width, height: image.height };\n resolve(size);\n });\n image.addEventListener(\"error\", reject);\n });\n }\n }\n\n // -----------------------------------------------------------------------------\n // SpreadSheet\n // -----------------------------------------------------------------------------\n css /* scss */ `\n .o-spreadsheet-bottom-bar {\n background-color: ${BACKGROUND_GRAY_COLOR};\n padding-left: ${HEADER_WIDTH}px;\n display: flex;\n align-items: center;\n font-size: 15px;\n border-top: 1px solid lightgrey;\n overflow: hidden;\n\n .o-add-sheet,\n .o-list-sheets {\n margin-right: 5px;\n }\n\n .o-add-sheet.disabled {\n cursor: not-allowed;\n }\n\n .o-sheet-item {\n display: flex;\n align-items: center;\n padding: 5px;\n cursor: pointer;\n &:hover {\n background-color: rgba(0, 0, 0, 0.08);\n }\n }\n\n .o-all-sheets {\n display: flex;\n align-items: center;\n max-width: 80%;\n overflow: hidden;\n }\n\n .o-sheet {\n color: #666;\n padding: 0 15px;\n padding-right: 10px;\n height: ${BOTTOMBAR_HEIGHT}px;\n line-height: ${BOTTOMBAR_HEIGHT}px;\n user-select: none;\n white-space: nowrap;\n border-left: 1px solid #c1c1c1;\n\n &:last-child {\n border-right: 1px solid #c1c1c1;\n }\n\n &.active {\n color: #484;\n background-color: #ffffff;\n box-shadow: 0 1px 3px 1px rgba(60, 64, 67, 0.15);\n }\n\n .o-sheet-icon {\n margin-left: 5px;\n\n &:hover {\n background-color: rgba(0, 0, 0, 0.08);\n }\n }\n }\n\n .o-selection-statistic {\n background-color: #ffffff;\n margin-left: auto;\n font-size: 14px;\n margin-right: 20px;\n padding: 4px 8px;\n color: #333;\n border-radius: 3px;\n box-shadow: 0 1px 3px 1px rgba(60, 64, 67, 0.15);\n user-select: none;\n cursor: pointer;\n &:hover {\n background-color: rgba(0, 0, 0, 0.08);\n }\n }\n\n .fade-enter-active {\n transition: opacity 0.5s;\n }\n\n .fade-enter {\n opacity: 0;\n }\n }\n`;\n class BottomBar extends owl.Component {\n constructor() {\n super(...arguments);\n this.bottomBarRef = owl.useRef(\"bottomBar\");\n this.menuState = owl.useState({\n isOpen: false,\n menuId: undefined,\n position: null,\n menuItems: [],\n });\n this.selectedStatisticFn = \"\";\n }\n setup() {\n owl.onMounted(() => this.focusSheet());\n owl.onPatched(() => this.focusSheet());\n }\n focusSheet() {\n const div = this.bottomBarRef.el.querySelector(`[data-id=\"${this.env.model.getters.getActiveSheetId()}\"]`);\n if (div && div.scrollIntoView) {\n div.scrollIntoView();\n }\n }\n addSheet() {\n const activeSheetId = this.env.model.getters.getActiveSheetId();\n const position = this.env.model.getters.getSheetIds().findIndex((sheetId) => sheetId === activeSheetId) + 1;\n const sheetId = this.env.model.uuidGenerator.uuidv4();\n const name = this.env.model.getters.getNextSheetName(this.env._t(\"Sheet\"));\n this.env.model.dispatch(\"CREATE_SHEET\", { sheetId, position, name });\n this.env.model.dispatch(\"ACTIVATE_SHEET\", { sheetIdFrom: activeSheetId, sheetIdTo: sheetId });\n }\n getVisibleSheets() {\n return this.env.model.getters\n .getVisibleSheetIds()\n .map((sheetId) => this.env.model.getters.getSheet(sheetId));\n }\n listSheets(ev) {\n const registry = new MenuItemRegistry();\n const from = this.env.model.getters.getActiveSheetId();\n let i = 0;\n for (const sheetId of this.env.model.getters.getSheetIds()) {\n const sheet = this.env.model.getters.getSheet(sheetId);\n registry.add(sheetId, {\n name: sheet.name,\n sequence: i,\n isReadonlyAllowed: true,\n textColor: sheet.isVisible ? undefined : \"grey\",\n action: (env) => {\n env.model.dispatch(\"ACTIVATE_SHEET\", { sheetIdFrom: from, sheetIdTo: sheetId });\n },\n });\n i++;\n }\n const target = ev.currentTarget;\n const { top, left } = target.getBoundingClientRect();\n this.openContextMenu(left, top, registry);\n }\n activateSheet(name) {\n this.env.model.dispatch(\"ACTIVATE_SHEET\", {\n sheetIdFrom: this.env.model.getters.getActiveSheetId(),\n sheetIdTo: name,\n });\n }\n onDblClick(sheetId) {\n interactiveRenameSheet(this.env, sheetId);\n }\n openContextMenu(x, y, registry, menuId) {\n this.menuState.isOpen = true;\n this.menuState.menuItems = registry.getAll().filter((x) => x.isVisible(this.env));\n this.menuState.position = { x, y };\n this.menuState.menuId = menuId;\n }\n onIconClick(sheetId, ev) {\n if (this.env.model.getters.getActiveSheetId() !== sheetId) {\n this.activateSheet(sheetId);\n }\n if (ev.closedMenuId === sheetId) {\n this.menuState.isOpen = false;\n this.menuState.menuId = undefined;\n }\n else {\n const target = ev.currentTarget.parentElement;\n const { top, left } = target.getBoundingClientRect();\n this.openContextMenu(left, top, sheetMenuRegistry, sheetId);\n }\n }\n onContextMenu(sheetId, ev) {\n if (this.env.model.getters.getActiveSheetId() !== sheetId) {\n this.activateSheet(sheetId);\n }\n const target = ev.currentTarget;\n const { top, left } = target.getBoundingClientRect();\n this.openContextMenu(left, top, sheetMenuRegistry, sheetId);\n }\n getSelectedStatistic() {\n const statisticFnResults = this.env.model.getters.getStatisticFnResults();\n // don't display button if no function has a result\n if (Object.values(statisticFnResults).every((result) => result === undefined)) {\n return undefined;\n }\n if (this.selectedStatisticFn === \"\") {\n this.selectedStatisticFn = Object.keys(statisticFnResults)[0];\n }\n return this.getComposedFnName(this.selectedStatisticFn, statisticFnResults[this.selectedStatisticFn]);\n }\n listSelectionStatistics(ev) {\n const registry = new MenuItemRegistry();\n let i = 0;\n for (let [fnName, fnValue] of Object.entries(this.env.model.getters.getStatisticFnResults())) {\n registry.add(fnName, {\n name: this.getComposedFnName(fnName, fnValue),\n sequence: i,\n isReadonlyAllowed: true,\n action: () => {\n this.selectedStatisticFn = fnName;\n },\n });\n i++;\n }\n const target = ev.currentTarget;\n const { top, left, width } = target.getBoundingClientRect();\n this.openContextMenu(left + width, top, registry);\n }\n getComposedFnName(fnName, fnValue) {\n return fnName + \": \" + (fnValue !== undefined ? formatValue(fnValue) : \"__\");\n }\n }\n BottomBar.template = \"o-spreadsheet-BottomBar\";\n BottomBar.components = { Menu };\n BottomBar.props = {\n onClick: Function,\n };\n\n css /* scss */ `\n .o-dashboard-clickable-cell {\n position: absolute;\n cursor: pointer;\n }\n`;\n let tKey = 1;\n class SpreadsheetDashboard extends owl.Component {\n setup() {\n const gridRef = owl.useRef(\"grid\");\n this.canvasPosition = useAbsoluteBoundingRect(gridRef);\n this.hoveredCell = owl.useState({ col: undefined, row: undefined });\n owl.useChildSubEnv({ getPopoverContainerRect: () => this.getGridRect() });\n useGridDrawing(\"canvas\", this.env.model, () => this.env.model.getters.getSheetViewDimension());\n this.onMouseWheel = useWheelHandler((deltaX, deltaY) => {\n this.moveCanvas(deltaX, deltaY);\n this.hoveredCell.col = undefined;\n this.hoveredCell.row = undefined;\n });\n }\n onCellHovered({ col, row }) {\n this.hoveredCell.col = col;\n this.hoveredCell.row = row;\n }\n get gridContainer() {\n const sheetId = this.env.model.getters.getActiveSheetId();\n const { right } = this.env.model.getters.getSheetZone(sheetId);\n const { end } = this.env.model.getters.getColDimensions(sheetId, right);\n return `\n max-width: ${end}px;\n `;\n }\n get gridOverlayDimensions() {\n return `\n height: 100%;\n width: 100%\n `;\n }\n getCellClickableStyle(coordinates) {\n return `\n top: ${coordinates.y}px;\n left: ${coordinates.x}px;\n width: ${coordinates.width}px;\n height: ${coordinates.height}px;\n `;\n }\n /**\n * Get all the boxes for the cell in the sheet view that are clickable.\n * This function is used to render an overlay over each clickable cell in\n * order to display a pointer cursor.\n *\n */\n getClickableCells() {\n const cells = [];\n const sheetId = this.env.model.getters.getActiveSheetId();\n for (const col of this.env.model.getters.getSheetViewVisibleCols()) {\n for (const row of this.env.model.getters.getSheetViewVisibleRows()) {\n const position = { sheetId, col, row };\n const action = this.getClickableAction(position);\n if (!action) {\n continue;\n }\n let zone;\n if (this.env.model.getters.isInMerge(position)) {\n zone = this.env.model.getters.getMerge(position);\n }\n else {\n zone = positionToZone({ col, row });\n }\n const rect = this.env.model.getters.getVisibleRect(zone);\n cells.push({\n coordinates: rect,\n position: { col, row },\n action,\n // we can't rely on position only because a row or a column could\n // be inserted at any time.\n tKey: `${tKey}-${col}-${row}`,\n });\n }\n }\n tKey++;\n return cells;\n }\n getClickableAction(position) {\n for (const items of clickableCellRegistry.getAll().sort((a, b) => a.sequence - b.sequence)) {\n if (items.condition(position, this.env)) {\n return items.action;\n }\n }\n return false;\n }\n selectClickableCell(clickableCell) {\n const { position, action } = clickableCell;\n action({ ...position, sheetId: this.env.model.getters.getActiveSheetId() }, this.env);\n }\n onClosePopover() {\n this.env.model.dispatch(\"CLOSE_CELL_POPOVER\");\n }\n onGridResized({ height, width }) {\n this.env.model.dispatch(\"RESIZE_SHEETVIEW\", {\n width: width,\n height: height,\n gridOffsetX: 0,\n gridOffsetY: 0,\n });\n }\n moveCanvas(deltaX, deltaY) {\n const { scrollX, scrollY } = this.env.model.getters.getActiveSheetDOMScrollInfo();\n this.env.model.dispatch(\"SET_VIEWPORT_OFFSET\", {\n offsetX: scrollX + deltaX,\n offsetY: scrollY + deltaY,\n });\n }\n getGridRect() {\n return { ...this.canvasPosition, ...this.env.model.getters.getSheetViewDimensionWithHeaders() };\n }\n }\n SpreadsheetDashboard.template = \"o-spreadsheet-SpreadsheetDashboard\";\n SpreadsheetDashboard.components = {\n GridOverlay,\n GridPopover,\n Popover,\n VerticalScrollBar,\n HorizontalScrollBar,\n FilterIconsOverlay,\n };\n SpreadsheetDashboard.props = {};\n\n css /* scss */ `\n .o-sidePanel {\n display: flex;\n flex-direction: column;\n overflow-x: hidden;\n background-color: white;\n border: 1px solid darkgray;\n user-select: none;\n .o-sidePanelHeader {\n padding: 6px;\n height: 30px;\n background-color: ${BACKGROUND_HEADER_COLOR};\n display: flex;\n align-items: center;\n justify-content: space-between;\n border-bottom: 1px solid darkgray;\n border-top: 1px solid darkgray;\n font-weight: bold;\n .o-sidePanelTitle {\n font-weight: bold;\n padding: 5px 10px;\n color: dimgrey;\n }\n .o-sidePanelClose {\n font-size: 1.5rem;\n padding: 5px 10px;\n cursor: pointer;\n &:hover {\n background-color: WhiteSmoke;\n }\n }\n }\n .o-sidePanelBody {\n overflow: auto;\n width: 100%;\n height: 100%;\n\n .o-section {\n padding: 16px;\n\n .o-section-title {\n font-weight: bold;\n color: dimgrey;\n margin-bottom: 5px;\n }\n\n .o-section-subtitle {\n color: dimgrey;\n font-weight: 500;\n font-size: 12px;\n line-height: 14px;\n margin: 8px 0 4px 0;\n }\n\n .o-subsection-left {\n display: inline-block;\n width: 47%;\n margin-right: 3%;\n }\n\n .o-subsection-right {\n display: inline-block;\n width: 47%;\n margin-left: 3%;\n }\n }\n }\n\n .o-sidepanel-error {\n color: red;\n margin-top: 10px;\n }\n\n .o-sidePanelButtons {\n padding: 16px;\n text-align: right;\n }\n\n .o-sidePanelButton {\n border: 1px solid lightgrey;\n padding: 0px 20px 0px 20px;\n border-radius: 4px;\n font-weight: 500;\n font-size: 14px;\n height: 30px;\n line-height: 16px;\n background: white;\n margin-right: 8px;\n &:hover:enabled {\n background-color: rgba(0, 0, 0, 0.08);\n }\n }\n .o-sidePanelButton:enabled {\n cursor: pointer;\n }\n .o-sidePanelButton:last-child {\n margin-right: 0px;\n }\n\n .o-input {\n color: #666666;\n border-radius: 4px;\n min-width: 0px;\n padding: 4px 6px;\n box-sizing: border-box;\n line-height: 1;\n width: 100%;\n height: 28px;\n .o-type-selector {\n background-position: right 5px top 11px;\n }\n }\n input.o-required,\n select.o-required {\n border-color: #4c4c4c;\n }\n input.o-optional,\n select.o-optional {\n border: 1px solid #a9a9a9;\n }\n input.o-invalid {\n border-color: red;\n }\n select.o-input {\n background-color: white;\n text-align: left;\n }\n\n .o-inflection {\n .o-inflection-icon-button {\n display: inline-block;\n border: 1px solid #dadce0;\n border-radius: 4px;\n cursor: pointer;\n padding: 1px 2px;\n }\n .o-inflection-icon-button:hover {\n background-color: rgba(0, 0, 0, 0.08);\n }\n table {\n table-layout: fixed;\n margin-top: 2%;\n display: table;\n text-align: left;\n font-size: 12px;\n line-height: 18px;\n width: 100%;\n }\n th.o-inflection-iconset-icons {\n width: 8%;\n }\n th.o-inflection-iconset-text {\n width: 28%;\n }\n th.o-inflection-iconset-operator {\n width: 14%;\n }\n th.o-inflection-iconset-type {\n width: 28%;\n }\n th.o-inflection-iconset-value {\n width: 26%;\n }\n input,\n select {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n }\n }\n\n .o-dropdown {\n position: relative;\n .o-dropdown-content {\n position: absolute;\n top: calc(100% + 5px);\n left: 0;\n z-index: ${ComponentsImportance.Dropdown};\n box-shadow: 1px 2px 5px 2px rgba(51, 51, 51, 0.15);\n background-color: #f6f6f6;\n\n .o-dropdown-item {\n padding: 7px 10px;\n }\n .o-dropdown-item:hover {\n background-color: rgba(0, 0, 0, 0.08);\n }\n .o-dropdown-line {\n display: flex;\n padding: 3px 6px;\n .o-line-item {\n width: 16px;\n height: 16px;\n margin: 1px 3px;\n &:hover {\n background-color: rgba(0, 0, 0, 0.08);\n }\n }\n }\n }\n }\n\n .o-tools {\n color: #333;\n font-size: 13px;\n cursor: default;\n display: flex;\n\n .o-tool {\n display: flex;\n align-items: center;\n margin: 2px;\n padding: 0 3px;\n border-radius: 2px;\n }\n\n .o-tool.active,\n .o-tool:not(.o-disabled):hover {\n background-color: rgba(0, 0, 0, 0.08);\n }\n\n .o-with-color > span {\n border-bottom: 4px solid;\n height: 16px;\n margin-top: 2px;\n }\n .o-with-color {\n .o-line-item:hover {\n outline: 1px solid gray;\n }\n }\n .o-border {\n .o-line-item {\n padding: 4px;\n margin: 1px;\n }\n }\n }\n }\n`;\n class SidePanel extends owl.Component {\n setup() {\n this.state = owl.useState({\n panel: sidePanelRegistry.get(this.props.component),\n });\n owl.onWillUpdateProps((nextProps) => (this.state.panel = sidePanelRegistry.get(nextProps.component)));\n }\n getTitle() {\n return typeof this.state.panel.title === \"function\"\n ? this.state.panel.title(this.env)\n : this.state.panel.title;\n }\n }\n SidePanel.template = \"o-spreadsheet-SidePanel\";\n SidePanel.props = {\n component: String,\n panelProps: { type: Object, optional: true },\n onCloseSidePanel: Function,\n };\n\n const AddMergeInteractiveContent = {\n MergeIsDestructive: _lt(\"Merging these cells will only preserve the top-leftmost value. Merge anyway?\"),\n MergeInFilter: _lt(\"You can't merge cells inside of an existing filter.\"),\n };\n function interactiveAddMerge(env, sheetId, target) {\n const result = env.model.dispatch(\"ADD_MERGE\", { sheetId, target });\n if (result.isCancelledBecause(80 /* CommandResult.MergeInFilter */)) {\n env.raiseError(AddMergeInteractiveContent.MergeInFilter);\n }\n else if (result.isCancelledBecause(3 /* CommandResult.MergeIsDestructive */)) {\n env.askConfirmation(AddMergeInteractiveContent.MergeIsDestructive, () => {\n env.model.dispatch(\"ADD_MERGE\", { sheetId, target, force: true });\n });\n }\n }\n\n const FORMATS = [\n { name: \"automatic\", text: NumberFormatTerms.Automatic },\n { name: \"number\", text: NumberFormatTerms.Number, description: \"1,000.12\", value: \"#,##0.00\" },\n { name: \"percent\", text: NumberFormatTerms.Percent, description: \"10.12%\", value: \"0.00%\" },\n {\n name: \"currency\",\n text: NumberFormatTerms.Currency,\n description: \"$1,000.12\",\n value: \"[$$]#,##0.00\",\n },\n {\n name: \"currency_rounded\",\n text: NumberFormatTerms.CurrencyRounded,\n description: \"$1,000\",\n value: \"[$$]#,##0\",\n },\n { name: \"date\", text: NumberFormatTerms.Date, description: \"9/26/2008\", value: \"m/d/yyyy\" },\n { name: \"time\", text: NumberFormatTerms.Time, description: \"10:43:00 PM\", value: \"hh:mm:ss a\" },\n {\n name: \"datetime\",\n text: NumberFormatTerms.DateTime,\n description: \"9/26/2008 22:43:00\",\n value: \"m/d/yyyy hh:mm:ss\",\n },\n {\n name: \"duration\",\n text: NumberFormatTerms.Duration,\n description: \"27:51:38\",\n value: \"hhhh:mm:ss\",\n },\n ];\n const CUSTOM_FORMATS = [\n { name: \"custom_currency\", text: NumberFormatTerms.CustomCurrency, sidePanel: \"CustomCurrency\" },\n ];\n // -----------------------------------------------------------------------------\n // TopBar\n // -----------------------------------------------------------------------------\n css /* scss */ `\n .o-spreadsheet-topbar {\n background-color: white;\n line-height: 1.2;\n display: flex;\n flex-direction: column;\n font-size: 13px;\n line-height: 1.2;\n user-select: none;\n\n .o-topbar-top {\n border-bottom: 1px solid #e0e2e4;\n display: flex;\n padding: 2px 10px;\n justify-content: space-between;\n\n /* Menus */\n .o-topbar-topleft {\n display: flex;\n .o-topbar-menu {\n padding: 4px 6px;\n margin: 0 2px;\n cursor: pointer;\n }\n\n .o-topbar-menu:hover,\n .o-topbar-menu-active {\n background-color: #f1f3f4;\n border-radius: 2px;\n }\n }\n\n .o-topbar-topright {\n display: flex;\n justify-content: flex-end;\n }\n }\n /* Toolbar + Cell Content */\n .o-topbar-toolbar {\n border-bottom: 1px solid #e0e2e4;\n display: flex;\n\n .o-readonly-toolbar {\n display: flex;\n align-items: center;\n background-color: ${BACKGROUND_HEADER_COLOR};\n padding-left: 18px;\n padding-right: 18px;\n }\n .o-composer-container {\n height: 34px;\n border: 1px solid #e0e2e4;\n margin-top: -1px;\n margin-bottom: -1px;\n }\n\n /* Toolbar */\n .o-toolbar-tools {\n display: flex;\n flex-shrink: 0;\n margin-left: 16px;\n color: #333;\n cursor: default;\n\n .o-tool {\n display: flex;\n align-items: center;\n margin: 2px;\n padding: 0px 3px;\n border-radius: 2px;\n cursor: pointer;\n min-width: fit-content;\n }\n\n .o-tool-outlined {\n background-color: rgba(0, 0, 0, 0.08);\n }\n\n .o-filter-tool {\n margin-right: 8px;\n }\n\n .o-tool.active,\n .o-tool:not(.o-disabled):hover {\n background-color: #f1f3f4;\n }\n\n .o-with-color > span {\n border-bottom: 4px solid;\n height: 16px;\n margin-top: 2px;\n }\n\n .o-with-color {\n .o-line-item:hover {\n outline: 1px solid gray;\n }\n }\n\n .o-border-dropdown {\n padding: 4px;\n }\n\n .o-divider {\n display: inline-block;\n border-right: 1px solid #e0e2e4;\n width: 0;\n margin: 0 6px;\n }\n\n .o-disabled {\n opacity: 0.6;\n cursor: default;\n }\n\n .o-dropdown {\n position: relative;\n display: flex;\n align-items: center;\n\n .o-dropdown-button {\n height: 30px;\n }\n\n .o-text-icon {\n height: 100%;\n line-height: 30px;\n }\n\n .o-text-options > div {\n line-height: 26px;\n padding: 3px 12px;\n &:hover {\n background-color: rgba(0, 0, 0, 0.08);\n }\n }\n\n .o-dropdown-content {\n position: absolute;\n top: 100%;\n left: 0;\n overflow-y: auto;\n overflow-x: hidden;\n z-index: ${ComponentsImportance.Dropdown};\n box-shadow: 1px 2px 5px 2px rgba(51, 51, 51, 0.15);\n background-color: white;\n\n .o-dropdown-item {\n cursor: pointer;\n }\n\n .o-dropdown-item:hover {\n background-color: rgba(0, 0, 0, 0.08);\n }\n\n .o-dropdown-line {\n display: flex;\n margin: 1px;\n\n .o-line-item {\n padding: 4px;\n width: 18px;\n height: 18px;\n cursor: pointer;\n\n &:hover {\n background-color: rgba(0, 0, 0, 0.08);\n }\n }\n }\n\n &.o-format-tool {\n padding: 5px 0;\n width: 250px;\n font-size: 12px;\n > div {\n padding: 0 20px;\n white-space: nowrap;\n\n &.active:before {\n content: \"\u2713\";\n font-weight: bold;\n position: absolute;\n left: 5px;\n }\n }\n }\n\n .o-dropdown-align-item {\n padding: 7px 10px;\n }\n }\n }\n }\n\n /* Cell Content */\n .o-toolbar-cell-content {\n font-size: 12px;\n font-weight: 500;\n padding: 0 12px;\n margin: 0;\n line-height: 34px;\n white-space: nowrap;\n user-select: text;\n }\n }\n }\n`;\n class TopBar extends owl.Component {\n constructor() {\n super(...arguments);\n this.DEFAULT_FONT_SIZE = DEFAULT_FONT_SIZE;\n this.commonFormats = FORMATS;\n this.customFormats = CUSTOM_FORMATS;\n this.currentFormatName = \"automatic\";\n this.fontSizes = fontSizes;\n this.style = {};\n this.state = owl.useState({\n menuState: { isOpen: false, position: null, menuItems: [] },\n activeTool: \"\",\n });\n this.isSelectingMenu = false;\n this.openedEl = null;\n this.inMerge = false;\n this.cannotMerge = false;\n this.undoTool = false;\n this.redoTool = false;\n this.paintFormatTool = false;\n this.fillColor = \"#ffffff\";\n this.textColor = \"#000000\";\n this.menus = [];\n this.composerStyle = `\n line-height: 34px;\n padding-left: 8px;\n height: 34px;\n background-color: white;\n `;\n }\n get dropdownStyle() {\n return `max-height:${this.props.dropdownMaxHeight}px`;\n }\n setup() {\n owl.useExternalListener(window, \"click\", this.onExternalClick);\n owl.onWillStart(() => this.updateCellState());\n owl.onWillUpdateProps(() => this.updateCellState());\n }\n get topbarComponents() {\n return topbarComponentRegistry\n .getAll()\n .filter((item) => !item.isVisible || item.isVisible(this.env));\n }\n onExternalClick(ev) {\n // TODO : manage click events better. We need this piece of code\n // otherwise the event opening the menu would close it on the same frame.\n // And we cannot stop the event propagation because it's used in an\n // external listener of the Menu component to close the context menu when\n // clicking on the top bar\n if (this.openedEl === ev.target) {\n return;\n }\n this.closeMenus();\n }\n onClick() {\n this.props.onClick();\n this.closeMenus();\n }\n toggleStyle(style) {\n setStyle(this.env, { [style]: !this.style[style] });\n }\n toggleFormat(formatName) {\n const formatter = FORMATS.find((f) => f.name === formatName);\n const value = (formatter && formatter.value) || \"\";\n setFormatter(this.env, value);\n }\n toggleAlign(align) {\n setStyle(this.env, { [\"align\"]: align });\n this.onClick();\n }\n onMenuMouseOver(menu, ev) {\n if (this.isSelectingMenu) {\n this.openMenu(menu, ev);\n }\n }\n toggleDropdownTool(tool, ev) {\n const isOpen = this.state.activeTool === tool;\n this.closeMenus();\n this.state.activeTool = isOpen ? \"\" : tool;\n this.openedEl = isOpen ? null : ev.target;\n }\n toggleContextMenu(menu, ev) {\n if (this.state.menuState.isOpen) {\n this.closeMenus();\n }\n else {\n this.openMenu(menu, ev);\n }\n }\n openMenu(menu, ev) {\n const { left, top, height } = ev.target.getBoundingClientRect();\n this.state.menuState.isOpen = true;\n this.state.menuState.position = { x: left, y: top + height };\n this.state.menuState.menuItems = getMenuChildren(menu, this.env).filter((item) => !item.isVisible || item.isVisible(this.env));\n this.state.menuState.parentMenu = menu;\n this.isSelectingMenu = true;\n this.openedEl = ev.target;\n this.env.model.dispatch(\"STOP_EDITION\");\n }\n closeMenus() {\n this.state.activeTool = \"\";\n this.state.menuState.isOpen = false;\n this.state.menuState.parentMenu = undefined;\n this.isSelectingMenu = false;\n this.openedEl = null;\n }\n updateCellState() {\n const zones = this.env.model.getters.getSelectedZones();\n const { col, row, sheetId } = this.env.model.getters.getActivePosition();\n this.inMerge = false;\n const { top, left, right, bottom } = this.env.model.getters.getSelectedZone();\n const { xSplit, ySplit } = this.env.model.getters.getPaneDivisions(sheetId);\n this.cannotMerge =\n zones.length > 1 ||\n (top === bottom && left === right) ||\n (left < xSplit && xSplit <= right) ||\n (top < ySplit && ySplit <= bottom);\n if (!this.cannotMerge) {\n const zone = this.env.model.getters.expandZone(sheetId, positionToZone({ col, row }));\n this.inMerge = isEqual(zones[0], zone);\n }\n this.undoTool = this.env.model.getters.canUndo();\n this.redoTool = this.env.model.getters.canRedo();\n this.paintFormatTool = this.env.model.getters.isPaintingFormat();\n const cell = this.env.model.getters.getActiveCell();\n if (cell.format) {\n const currentFormat = this.commonFormats.find((f) => f.value === cell.format);\n this.currentFormatName = currentFormat ? currentFormat.name : \"\";\n }\n else {\n this.currentFormatName = \"automatic\";\n }\n this.style = { ...this.env.model.getters.getCurrentStyle() };\n this.style.align = this.style.align || cell.defaultAlign;\n this.fillColor = this.style.fillColor || \"#ffffff\";\n this.textColor = this.style.textColor || \"#000000\";\n this.menus = topbarMenuRegistry\n .getAll()\n .filter((item) => !item.isVisible || item.isVisible(this.env));\n }\n getMenuName(menu) {\n return getMenuName(menu, this.env);\n }\n toggleMerge() {\n if (this.cannotMerge) {\n return;\n }\n const zones = this.env.model.getters.getSelectedZones();\n const target = [zones[zones.length - 1]];\n const sheetId = this.env.model.getters.getActiveSheetId();\n if (this.inMerge) {\n this.env.model.dispatch(\"REMOVE_MERGE\", { sheetId, target });\n }\n else {\n interactiveAddMerge(this.env, sheetId, target);\n }\n }\n setColor(target, color) {\n setStyle(this.env, { [target]: color });\n this.onClick();\n }\n setBorder(command) {\n this.env.model.dispatch(\"SET_FORMATTING\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n target: this.env.model.getters.getSelectedZones(),\n border: command,\n });\n this.onClick();\n }\n setFormat(format, custom) {\n if (!custom) {\n this.toggleFormat(format);\n }\n else {\n this.openCustomFormatSidePanel(format);\n }\n this.onClick();\n }\n openCustomFormatSidePanel(custom) {\n const customFormatter = CUSTOM_FORMATS.find((c) => c.name === custom);\n const sidePanel = (customFormatter && customFormatter.sidePanel) || \"\";\n this.env.openSidePanel(sidePanel);\n }\n setDecimal(step) {\n this.env.model.dispatch(\"SET_DECIMAL\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n target: this.env.model.getters.getSelectedZones(),\n step: step,\n });\n }\n paintFormat() {\n this.env.model.dispatch(\"ACTIVATE_PAINT_FORMAT\", {\n target: this.env.model.getters.getSelectedZones(),\n });\n }\n clearFormatting() {\n this.env.model.dispatch(\"CLEAR_FORMATTING\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n target: this.env.model.getters.getSelectedZones(),\n });\n }\n setSize(fontSizeStr) {\n const fontSize = parseFloat(fontSizeStr);\n setStyle(this.env, { fontSize });\n this.onClick();\n }\n doAction(action) {\n action(this.env);\n this.closeMenus();\n }\n undo() {\n this.env.model.dispatch(\"REQUEST_UNDO\");\n }\n redo() {\n this.env.model.dispatch(\"REQUEST_REDO\");\n }\n get selectionContainsFilter() {\n const sheetId = this.env.model.getters.getActiveSheetId();\n const selectedZones = this.env.model.getters.getSelectedZones();\n return this.env.model.getters.doesZonesContainFilter(sheetId, selectedZones);\n }\n get cannotCreateFilter() {\n return !areZonesContinuous(...this.env.model.getters.getSelectedZones());\n }\n createFilter() {\n if (this.cannotCreateFilter) {\n return;\n }\n const sheetId = this.env.model.getters.getActiveSheetId();\n const selection = this.env.model.getters.getSelectedZones();\n interactiveAddFilter(this.env, sheetId, selection);\n }\n removeFilter() {\n this.env.model.dispatch(\"REMOVE_FILTER_TABLE\", {\n sheetId: this.env.model.getters.getActiveSheetId(),\n target: this.env.model.getters.getSelectedZones(),\n });\n }\n }\n TopBar.template = \"o-spreadsheet-TopBar\";\n TopBar.components = { ColorPicker, Menu, Composer };\n TopBar.props = {\n onClick: Function,\n focusComposer: String,\n onComposerContentFocused: Function,\n dropdownMaxHeight: Number,\n };\n\n function instantiateClipboard() {\n return new WebClipboardWrapper(navigator.clipboard);\n }\n class WebClipboardWrapper {\n // Can be undefined because navigator.clipboard doesn't exist in old browsers\n constructor(clipboard) {\n this.clipboard = clipboard;\n }\n async write(clipboardContent) {\n var _a;\n try {\n (_a = this.clipboard) === null || _a === void 0 ? void 0 : _a.write(this.getClipboardItems(clipboardContent));\n }\n catch (e) { }\n }\n async writeText(text) {\n var _a;\n try {\n (_a = this.clipboard) === null || _a === void 0 ? void 0 : _a.writeText(text);\n }\n catch (e) { }\n }\n async readText() {\n let permissionResult = undefined;\n try {\n //@ts-ignore - clipboard-read is not implemented in all browsers\n permissionResult = await navigator.permissions.query({ name: \"clipboard-read\" });\n }\n catch (e) { }\n try {\n const clipboardContent = await this.clipboard.readText();\n return { status: \"ok\", content: clipboardContent };\n }\n catch (e) {\n const status = (permissionResult === null || permissionResult === void 0 ? void 0 : permissionResult.state) === \"denied\" ? \"permissionDenied\" : \"notImplemented\";\n return { status };\n }\n }\n getClipboardItems(content) {\n return [\n new ClipboardItem({\n [ClipboardMIMEType.PlainText]: this.getBlob(content, ClipboardMIMEType.PlainText),\n [ClipboardMIMEType.Html]: this.getBlob(content, ClipboardMIMEType.Html),\n }),\n ];\n }\n getBlob(clipboardContent, type) {\n return new Blob([clipboardContent[type] || \"\"], {\n type,\n });\n }\n }\n\n css /* scss */ `\n .o-spreadsheet {\n position: relative;\n display: grid;\n grid-template-columns: auto 350px;\n color: #333;\n input {\n background-color: white;\n }\n .text-muted {\n color: grey !important;\n }\n button {\n color: #333;\n }\n\n * {\n font-family: \"Roboto\", \"RobotoDraft\", Helvetica, Arial, sans-serif;\n }\n &,\n *,\n *:before,\n *:after {\n box-sizing: content-box;\n }\n .o-separator {\n border-bottom: ${MENU_SEPARATOR_BORDER_WIDTH}px solid #e0e2e4;\n margin-top: ${MENU_SEPARATOR_PADDING}px;\n margin-bottom: ${MENU_SEPARATOR_PADDING}px;\n }\n }\n\n .o-two-columns {\n grid-column: 1 / 3;\n }\n\n .o-icon {\n width: ${ICON_EDGE_LENGTH}px;\n height: ${ICON_EDGE_LENGTH}px;\n opacity: 0.6;\n vertical-align: middle;\n }\n\n .o-cf-icon {\n width: ${CF_ICON_EDGE_LENGTH}px;\n height: ${CF_ICON_EDGE_LENGTH}px;\n vertical-align: sub;\n }\n`;\n // -----------------------------------------------------------------------------\n // GRID STYLE\n // -----------------------------------------------------------------------------\n css /* scss */ `\n .o-grid {\n position: relative;\n overflow: hidden;\n background-color: ${BACKGROUND_GRAY_COLOR};\n &:focus {\n outline: none;\n }\n\n > canvas {\n border-top: 1px solid #e2e3e3;\n border-bottom: 1px solid #e2e3e3;\n }\n .o-scrollbar {\n &.corner {\n right: 0px;\n bottom: 0px;\n height: ${SCROLLBAR_WIDTH$1}px;\n width: ${SCROLLBAR_WIDTH$1}px;\n border-top: 1px solid #e2e3e3;\n border-left: 1px solid #e2e3e3;\n }\n }\n\n .o-grid-overlay {\n position: absolute;\n outline: none;\n }\n }\n`;\n const t = (s) => s;\n class Spreadsheet extends owl.Component {\n constructor() {\n super(...arguments);\n this.isViewportTooSmall = false;\n }\n get model() {\n return this.props.model;\n }\n getStyle() {\n if (this.env.isDashboard()) {\n return `grid-template-rows: auto;`;\n }\n return `grid-template-rows: ${TOPBAR_HEIGHT}px auto ${BOTTOMBAR_HEIGHT + 1}px`;\n }\n setup() {\n this.sidePanel = owl.useState({ isOpen: false, panelProps: {} });\n this.composer = owl.useState({\n topBarFocus: \"inactive\",\n gridFocusMode: \"inactive\",\n });\n this.keyDownMapping = {\n \"CTRL+H\": () => this.toggleSidePanel(\"FindAndReplace\", {}),\n \"CTRL+F\": () => this.toggleSidePanel(\"FindAndReplace\", {}),\n };\n const fileStore = this.model.config.external.fileStore;\n owl.useSubEnv({\n model: this.model,\n imageProvider: fileStore ? new ImageProvider(fileStore) : undefined,\n loadCurrencies: this.model.config.external.loadCurrencies,\n isDashboard: () => this.model.getters.isDashboard(),\n openSidePanel: this.openSidePanel.bind(this),\n toggleSidePanel: this.toggleSidePanel.bind(this),\n _t: Spreadsheet._t,\n clipboard: this.env.clipboard || instantiateClipboard(),\n });\n owl.useExternalListener(window, \"resize\", () => this.render(true));\n owl.useExternalListener(window, \"beforeunload\", this.unbindModelEvents.bind(this));\n this.bindModelEvents();\n owl.onMounted(() => {\n this.checkViewportSize();\n });\n owl.onWillUnmount(() => this.unbindModelEvents());\n owl.onPatched(() => {\n this.checkViewportSize();\n });\n }\n get focusTopBarComposer() {\n return this.model.getters.getEditionMode() === \"inactive\"\n ? \"inactive\"\n : this.composer.topBarFocus;\n }\n get focusGridComposer() {\n return this.model.getters.getEditionMode() === \"inactive\"\n ? \"inactive\"\n : this.composer.gridFocusMode;\n }\n bindModelEvents() {\n this.model.on(\"update\", this, () => this.render(true));\n this.model.on(\"notify-ui\", this, this.onNotifyUI);\n }\n unbindModelEvents() {\n this.model.off(\"update\", this);\n this.model.off(\"notify-ui\", this);\n }\n checkViewportSize() {\n const { xRatio, yRatio } = this.env.model.getters.getFrozenSheetViewRatio(this.env.model.getters.getActiveSheetId());\n if (yRatio > MAXIMAL_FREEZABLE_RATIO || xRatio > MAXIMAL_FREEZABLE_RATIO) {\n if (this.isViewportTooSmall) {\n return;\n }\n this.env.notifyUser({\n text: _lt(\"The current window is too small to display this sheet properly. Consider resizing your browser window or adjusting frozen rows and columns.\"),\n tag: \"viewportTooSmall\",\n });\n this.isViewportTooSmall = true;\n }\n else {\n this.isViewportTooSmall = false;\n }\n }\n onNotifyUI(payload) {\n switch (payload.type) {\n case \"ERROR\":\n this.env.raiseError(payload.text);\n break;\n }\n }\n openSidePanel(panel, panelProps) {\n this.sidePanel.component = panel;\n this.sidePanel.panelProps = panelProps;\n this.sidePanel.isOpen = true;\n }\n closeSidePanel() {\n this.sidePanel.isOpen = false;\n this.focusGrid();\n }\n toggleSidePanel(panel, panelProps) {\n if (this.sidePanel.isOpen && panel === this.sidePanel.component) {\n this.sidePanel.isOpen = false;\n this.focusGrid();\n }\n else {\n this.openSidePanel(panel, panelProps);\n }\n }\n focusGrid() {\n if (!this._focusGrid) {\n throw new Error(\"_focusGrid should be exposed by the grid component\");\n }\n this._focusGrid();\n }\n onKeydown(ev) {\n let keyDownString = \"\";\n if (ev.ctrlKey || ev.metaKey) {\n keyDownString += \"CTRL+\";\n }\n keyDownString += ev.key.toUpperCase();\n let handler = this.keyDownMapping[keyDownString];\n if (handler) {\n ev.preventDefault();\n ev.stopPropagation();\n handler();\n return;\n }\n }\n onTopBarComposerFocused(selection) {\n if (this.model.getters.isReadonly()) {\n return;\n }\n this.model.dispatch(\"UNFOCUS_SELECTION_INPUT\");\n this.composer.topBarFocus = \"contentFocus\";\n this.composer.gridFocusMode = \"inactive\";\n this.setComposerContent({ selection } || {});\n }\n onGridComposerContentFocused() {\n if (this.model.getters.isReadonly()) {\n return;\n }\n this.model.dispatch(\"UNFOCUS_SELECTION_INPUT\");\n this.composer.topBarFocus = \"inactive\";\n this.composer.gridFocusMode = \"contentFocus\";\n this.setComposerContent({});\n }\n onGridComposerCellFocused(content, selection) {\n if (this.model.getters.isReadonly()) {\n return;\n }\n this.model.dispatch(\"UNFOCUS_SELECTION_INPUT\");\n this.composer.topBarFocus = \"inactive\";\n this.composer.gridFocusMode = \"cellFocus\";\n this.setComposerContent({ content, selection } || {});\n }\n /**\n * Start the edition or update the content if it's already started.\n */\n setComposerContent({ content, selection, }) {\n if (this.model.getters.getEditionMode() === \"inactive\") {\n this.model.dispatch(\"START_EDITION\", { text: content, selection });\n }\n else if (content) {\n this.model.dispatch(\"SET_CURRENT_CONTENT\", { content, selection });\n }\n }\n get gridHeight() {\n const { height } = this.env.model.getters.getSheetViewDimension();\n return height;\n }\n }\n Spreadsheet.template = \"o-spreadsheet-Spreadsheet\";\n Spreadsheet.components = { TopBar, Grid, BottomBar, SidePanel, SpreadsheetDashboard };\n Spreadsheet._t = t;\n Spreadsheet.props = {\n model: Object,\n };\n\n class LocalTransportService {\n constructor() {\n this.listeners = [];\n }\n sendMessage(message) {\n for (const { callback } of this.listeners) {\n callback(message);\n }\n }\n onNewMessage(id, callback) {\n this.listeners.push({ id, callback });\n }\n leave(id) {\n this.listeners = this.listeners.filter((listener) => listener.id !== id);\n }\n }\n\n function inverseCommand(cmd) {\n return inverseCommandRegistry.get(cmd.type)(cmd);\n }\n\n /**\n * Create an empty structure according to the type of the node key:\n * string: object\n * number: array\n */\n function createEmptyStructure(node) {\n if (typeof node === \"string\") {\n return {};\n }\n else if (typeof node === \"number\") {\n return [];\n }\n throw new Error(`Cannot create new node`);\n }\n\n /**\n * A branch holds a sequence of operations.\n * It can be represented as \"A - B - C - D\" if A, B, C and D are executed one\n * after the other.\n *\n * @param buildTransformation Factory to build transformations\n * @param operations initial operations\n */\n class Branch {\n constructor(buildTransformation, operations = []) {\n this.buildTransformation = buildTransformation;\n this.operations = operations;\n }\n getOperations() {\n return this.operations;\n }\n getOperation(operationId) {\n const operation = this.operations.find((op) => op.id === operationId);\n if (!operation) {\n throw new Error(`Operation ${operationId} not found`);\n }\n return operation;\n }\n getLastOperationId() {\n var _a;\n return (_a = this.operations[this.operations.length - 1]) === null || _a === void 0 ? void 0 : _a.id;\n }\n /**\n * Get the id of the operation appears first in the list of operations\n */\n getFirstOperationAmong(op1, op2) {\n for (const operation of this.operations) {\n if (operation.id === op1)\n return op1;\n if (operation.id === op2)\n return op2;\n }\n throw new Error(`Operation ${op1} and ${op2} not found`);\n }\n contains(operationId) {\n return !!this.operations.find((operation) => operation.id === operationId);\n }\n /**\n * Add the given operation as the first operation\n */\n prepend(operation) {\n const transformation = this.buildTransformation.with(operation.data);\n this.operations = [\n operation,\n ...this.operations.map((operation) => operation.transformed(transformation)),\n ];\n }\n /**\n * add the given operation after the given predecessorOpId\n */\n insert(newOperation, predecessorOpId) {\n const transformation = this.buildTransformation.with(newOperation.data);\n const { before, operation, after } = this.locateOperation(predecessorOpId);\n this.operations = [\n ...before,\n operation,\n newOperation,\n ...after.map((operation) => operation.transformed(transformation)),\n ];\n }\n /**\n * Add the given operation as the last operation\n */\n append(operation) {\n this.operations.push(operation);\n }\n /**\n * Append operations in the given branch to this branch.\n */\n appendBranch(branch) {\n this.operations = this.operations.concat(branch.operations);\n }\n /**\n * Create and return a copy of this branch, starting after the given operationId\n */\n fork(operationId) {\n const { after } = this.locateOperation(operationId);\n return new Branch(this.buildTransformation, after);\n }\n /**\n * Transform all the operations in this branch with the given transformation\n */\n transform(transformation) {\n this.operations = this.operations.map((operation) => operation.transformed(transformation));\n }\n /**\n * Cut the branch before the operation, meaning the operation\n * and all following operations are dropped.\n */\n cutBefore(operationId) {\n this.operations = this.locateOperation(operationId).before;\n }\n /**\n * Cut the branch after the operation, meaning all following operations are dropped.\n */\n cutAfter(operationId) {\n const { before, operation } = this.locateOperation(operationId);\n this.operations = before.concat([operation]);\n }\n /**\n * Find an operation in this branch based on its id.\n * This returns the operation itself, operations which comes before it\n * and operation which comes after it.\n */\n locateOperation(operationId) {\n const operationIndex = this.operations.findIndex((step) => step.id === operationId);\n if (operationIndex === -1) {\n throw new Error(`Operation ${operationId} not found`);\n }\n return {\n before: this.operations.slice(0, operationIndex),\n operation: this.operations[operationIndex],\n after: this.operations.slice(operationIndex + 1),\n };\n }\n }\n\n /**\n * An Operation can be executed to change a data structure from state A\n * to state B.\n * It should hold the necessary data used to perform this transition.\n * It should be possible to revert the changes made by this operation.\n *\n * In the context of o-spreadsheet, the data from an operation would\n * be a revision (the commands are used to execute it, the `changes` are used\n * to revert it).\n */\n class Operation {\n constructor(id, data) {\n this.id = id;\n this.data = data;\n }\n transformed(transformation) {\n return new LazyOperation(this.id, lazy(() => transformation(this.data)));\n }\n }\n class LazyOperation {\n constructor(id, lazyData) {\n this.id = id;\n this.lazyData = lazyData;\n }\n get data() {\n return this.lazyData();\n }\n transformed(transformation) {\n return new LazyOperation(this.id, this.lazyData.map(transformation));\n }\n }\n\n /**\n * An execution object is a sequence of executionSteps (each execution step is an operation in a branch).\n *\n * You can iterate over the steps of an execution\n * ```js\n * for (const operation of execution) {\n * // ... do something\n * }\n * ```\n */\n class OperationSequence {\n constructor(operations) {\n this.operations = operations;\n }\n [Symbol.iterator]() {\n return this.operations[Symbol.iterator]();\n }\n /**\n * Stop the operation sequence at a given operation\n * @param operationId included\n */\n stopWith(operationId) {\n function* filter(execution, operationId) {\n for (const step of execution) {\n yield step;\n if (step.operation.id === operationId) {\n return;\n }\n }\n }\n return new OperationSequence(filter(this.operations, operationId));\n }\n /**\n * Stop the operation sequence before a given operation\n * @param operationId excluded\n */\n stopBefore(operationId) {\n function* filter(execution, operationId) {\n for (const step of execution) {\n if (step.operation.id === operationId) {\n return;\n }\n yield step;\n }\n }\n return new OperationSequence(filter(this.operations, operationId));\n }\n /**\n * Start the operation sequence at a given operation\n * @param operationId excluded\n */\n startAfter(operationId) {\n function* filter(execution, operationId) {\n let skip = true;\n for (const step of execution) {\n if (!skip) {\n yield step;\n }\n if (step.operation.id === operationId) {\n skip = false;\n }\n }\n }\n return new OperationSequence(filter(this.operations, operationId));\n }\n }\n\n /**\n * The tree is a data structure used to maintain the different branches of the\n * SelectiveHistory.\n *\n * Branches can be \"stacked\" on each other and an execution path can be derived\n * from any stack of branches. The rules to derive this path is explained below.\n *\n * An operation can be cancelled/undone by inserting a new branch below\n * this operation.\n * e.g\n * Given the branch A B C\n * To undo B, a new branching branch is inserted at operation B.\n * ```txt\n * A B C D\n * > C' D'\n * ```\n * A new execution path can now be derived. At each operation:\n * - if there is a lower branch, don't execute it and go to the operation below\n * - if not, execute it and go to the operation on the right.\n * The execution path is A C' D'\n * Operation C and D have been adapted (transformed) in the lower branch\n * since operation B is not executed in this branch.\n *\n */\n class Tree {\n constructor(buildTransformation, initialBranch) {\n this.buildTransformation = buildTransformation;\n this.branchingOperationIds = new Map();\n this.branches = [initialBranch];\n }\n /**\n * Return the last branch of the entire stack of branches.\n */\n getLastBranch() {\n return this.branches[this.branches.length - 1];\n }\n /**\n * Return the sequence of operations from this branch\n * until the very last branch.\n */\n execution(branch) {\n return new OperationSequence(linkNext(this._execution(branch), this._execution(branch)));\n }\n /**\n * Return the sequence of operations from this branch\n * to the very first branch.\n */\n revertedExecution(branch) {\n return new OperationSequence(linkNext(this._revertedExecution(branch), this._revertedExecution(branch)));\n }\n /**\n * Append an operation to the end of the tree.\n * Also insert the (transformed) operation in all previous branches.\n *\n * Adding operation `D` to the last branch\n * ```txt\n * A1 B1 C1\n * > B2 C2\n * ```\n * will give\n * ```txt\n * A1 B1 C1 D' with D' = D transformed with A1\n * > B2 C2 D\n * ```\n */\n insertOperationLast(branch, operation) {\n var _a;\n const insertAfter = branch.getLastOperationId() || ((_a = this.previousBranch(branch)) === null || _a === void 0 ? void 0 : _a.getLastOperationId());\n branch.append(operation);\n if (insertAfter) {\n this.insertPrevious(branch, operation, insertAfter);\n }\n }\n /**\n * Insert a new operation after an other operation.\n * The operation will be inserted in this branch, in next branches (transformed)\n * and in previous branches (also transformed).\n *\n * Given\n * ```txt\n * 1: A1 B1 C1\n * 2: > B2 C2\n * 3: > C3\n * ```\n * Inserting D to branch 2 gives\n * ```txt\n * 1: A1 B1 C1 D1 D1 = D transformed with A1\n * 2: > B2 C2 D with D = D\n * 3: > C3 D2 D2 = D transformed without B2 (B2\u207b\u00b9)\n * ```\n */\n insertOperationAfter(branch, operation, predecessorOpId) {\n branch.insert(operation, predecessorOpId);\n this.updateNextWith(branch, operation, predecessorOpId);\n this.insertPrevious(branch, operation, predecessorOpId);\n }\n /**\n * Create a new branching branch at the given operation.\n * This cancels the operation from the execution path.\n */\n undo(branch, operation) {\n const transformation = this.buildTransformation.without(operation.data);\n const branchingId = this.branchingOperationIds.get(branch);\n this.branchingOperationIds.set(branch, operation.id);\n const nextBranch = branch.fork(operation.id);\n if (branchingId) {\n this.branchingOperationIds.set(nextBranch, branchingId);\n }\n this.insertBranchAfter(branch, nextBranch);\n this.transform(nextBranch, transformation);\n }\n /**\n * Remove the branch just after this one. This un-cancels (redo) the branching\n * operation. Lower branches will be transformed accordingly.\n *\n * Given\n * ```txt\n * 1: A1 B1 C1\n * 2: > B2 C2\n * 3: > C3\n * ```\n * removing the next branch of 1 gives\n *\n * ```txt\n * 1: A1 B1 C1\n * 2: > C3' with C3' = C1 transformed without B1 (B1\u207b\u00b9)\n * ```\n */\n redo(branch) {\n const removedBranch = this.nextBranch(branch);\n if (!removedBranch)\n return;\n const nextBranch = this.nextBranch(removedBranch);\n this.removeBranchFromTree(removedBranch);\n const undoBranchingId = this.branchingOperationIds.get(removedBranch);\n if (undoBranchingId) {\n this.branchingOperationIds.set(branch, undoBranchingId);\n }\n else {\n this.branchingOperationIds.delete(branch);\n }\n if (nextBranch) {\n this.rebaseUp(nextBranch);\n }\n }\n /**\n * Drop the operation and all following operations in every\n * branch\n */\n drop(operationId) {\n for (const branch of this.branches) {\n if (branch.contains(operationId)) {\n branch.cutBefore(operationId);\n }\n }\n }\n /**\n * Find the operation in the execution path.\n */\n findOperation(branch, operationId) {\n for (const operation of this.revertedExecution(branch)) {\n if (operation.operation.id === operationId) {\n return operation;\n }\n }\n throw new Error(`Operation ${operationId} not found`);\n }\n /**\n * Rebuild transformed operations of this branch based on the upper branch.\n *\n * Given the following structure:\n * ```txt\n * 1: A1 B1 C1\n * 2: > B2 C2\n * 3: > C3\n * ```\n * Rebasing branch \"2\" gives\n * ```txt\n * 1: A1 B1 C1\n * 2: > B2' C2' With B2' = B1 transformed without A1 and C2' = C1 transformed without A1\n * 3: > C3' C3' = C2' transformed without B2'\n * ```\n */\n rebaseUp(branch) {\n const { previousBranch, branchingOperation } = this.findPreviousBranchingOperation(branch);\n if (!previousBranch || !branchingOperation)\n return;\n const rebaseTransformation = this.buildTransformation.without(branchingOperation.data);\n const newBranch = previousBranch.fork(branchingOperation.id);\n this.branchingOperationIds.set(newBranch, this.branchingOperationIds.get(branch));\n this.removeBranchFromTree(branch);\n this.insertBranchAfter(previousBranch, newBranch);\n newBranch.transform(rebaseTransformation);\n const nextBranch = this.nextBranch(newBranch);\n if (nextBranch) {\n this.rebaseUp(nextBranch);\n }\n }\n removeBranchFromTree(branch) {\n const index = this.branches.findIndex((l) => l === branch);\n this.branches.splice(index, 1);\n }\n insertBranchAfter(branch, toInsert) {\n const index = this.branches.findIndex((l) => l === branch);\n this.branches.splice(index + 1, 0, toInsert);\n }\n /**\n * Update the branching branch of this branch, either by (1) inserting the new\n * operation in it or (2) by transforming it.\n * (1) If the operation is positioned before the branching branch, the branching\n * branch should be transformed with this operation.\n * (2) If it's positioned after, the operation should be inserted in the\n * branching branch.\n */\n updateNextWith(branch, operation, predecessorOpId) {\n const branchingId = this.branchingOperationIds.get(branch);\n const nextBranch = this.nextBranch(branch);\n if (!branchingId || !nextBranch) {\n return;\n }\n if (branch.getFirstOperationAmong(predecessorOpId, branchingId) === branchingId) {\n const transformedOperation = this.addToNextBranch(branch, nextBranch, branchingId, operation, predecessorOpId);\n this.updateNextWith(nextBranch, transformedOperation, predecessorOpId);\n }\n else {\n const transformation = this.buildTransformation.with(operation.data);\n this.transform(nextBranch, transformation);\n }\n }\n addToNextBranch(branch, nextBranch, branchingId, operation, predecessorOpId) {\n // If the operation is inserted after the branching operation, it should\n // be positioned first.\n let transformedOperation = operation;\n if (predecessorOpId === branchingId) {\n transformedOperation = this.getTransformedOperation(branch, branchingId, operation);\n nextBranch.prepend(transformedOperation);\n }\n else if (nextBranch.contains(predecessorOpId)) {\n transformedOperation = this.getTransformedOperation(branch, branchingId, operation);\n nextBranch.insert(transformedOperation, predecessorOpId);\n }\n else {\n nextBranch.append(operation);\n }\n return transformedOperation;\n }\n getTransformedOperation(branch, branchingId, operation) {\n const branchingOperation = branch.getOperation(branchingId);\n const branchingTransformation = this.buildTransformation.without(branchingOperation.data);\n return operation.transformed(branchingTransformation);\n }\n /**\n * Check if this branch should execute the given operation.\n * i.e. If the operation is not cancelled by a branching branch.\n */\n shouldExecute(branch, operation) {\n return operation.id !== this.branchingOperationIds.get(branch);\n }\n transform(branch, transformation) {\n branch.transform(transformation);\n const nextBranch = this.nextBranch(branch);\n if (nextBranch) {\n this.transform(nextBranch, transformation);\n }\n }\n /**\n * Insert a new operation in previous branches. The operations which are\n * positioned after the inserted operations are transformed with the newly\n * inserted operations. This one is also transformed, with the branching\n * operation.\n */\n insertPrevious(branch, newOperation, insertAfter) {\n const { previousBranch, branchingOperation } = this.findPreviousBranchingOperation(branch);\n if (!previousBranch || !branchingOperation)\n return;\n const transformation = this.buildTransformation.with(branchingOperation.data);\n const branchTail = branch.fork(insertAfter);\n branchTail.transform(transformation);\n previousBranch.cutAfter(insertAfter);\n previousBranch.appendBranch(branchTail);\n const operationToInsert = newOperation.transformed(transformation);\n this.insertPrevious(previousBranch, operationToInsert, insertAfter);\n }\n findPreviousBranchingOperation(branch) {\n const previousBranch = this.previousBranch(branch);\n if (!previousBranch)\n return { previousBranch: undefined, branchingOperation: undefined };\n const previousBranchingId = this.branchingOperationIds.get(previousBranch);\n if (!previousBranchingId)\n return { previousBranch: undefined, branchingOperation: undefined };\n return {\n previousBranch,\n branchingOperation: previousBranch.getOperation(previousBranchingId),\n };\n }\n /**\n * Retrieve the next branch of the given branch\n */\n nextBranch(branch) {\n const index = this.branches.findIndex((l) => l === branch);\n if (index === -1) {\n return undefined;\n }\n return this.branches[index + 1];\n }\n /**\n * Retrieve the previous branch of the given branch\n */\n previousBranch(branch) {\n const index = this.branches.findIndex((l) => l === branch);\n if (index === -1) {\n return undefined;\n }\n return this.branches[index - 1];\n }\n /**\n * Yields the sequence of operations to execute, in reverse order.\n */\n *_revertedExecution(branch) {\n const branchingOperationId = this.branchingOperationIds.get(branch);\n let afterBranchingPoint = !!branchingOperationId;\n const operations = branch.getOperations();\n for (let i = operations.length - 1; i >= 0; i--) {\n const operation = operations[i];\n if (operation.id === branchingOperationId) {\n afterBranchingPoint = false;\n }\n if (!afterBranchingPoint) {\n yield {\n operation: operation,\n branch: branch,\n isCancelled: !this.shouldExecute(branch, operation),\n };\n }\n }\n const previous = this.previousBranch(branch);\n yield* previous ? this._revertedExecution(previous) : [];\n }\n /**\n * Yields the sequence of operations to execute\n */\n *_execution(branch) {\n for (const operation of branch.getOperations()) {\n yield {\n operation: operation,\n branch: branch,\n isCancelled: !this.shouldExecute(branch, operation),\n };\n if (operation.id === this.branchingOperationIds.get(branch)) {\n const next = this.nextBranch(branch);\n yield* next ? this._execution(next) : [];\n return;\n }\n }\n if (!this.branchingOperationIds.get(branch)) {\n const next = this.nextBranch(branch);\n yield* next ? this._execution(next) : [];\n }\n }\n }\n\n class SelectiveHistory {\n /**\n * The selective history is a data structure used to register changes/updates of a state.\n * Each change/update is called an \"operation\".\n * The data structure allows to easily cancel (and redo) any operation individually.\n * An operation can be represented by any data structure. It can be a \"command\", a \"diff\", etc.\n * However it must have the following properties:\n * - it can be applied to modify the state\n * - it can be reverted on the state such that it was never executed.\n * - it can be transformed given other operation (Operational Transformation)\n *\n * Since this data structure doesn't know anything about the state nor the structure of\n * operations, the actual work must be performed by external functions given as parameters.\n * @param initialOperationId\n * @param applyOperation a function which can apply an operation to the state\n * @param revertOperation a function which can revert an operation from the state\n * @param buildEmpty a function returning an \"empty\" operation.\n * i.e an operation that leaves the state unmodified once applied or reverted\n * (used for internal implementation)\n * @param buildTransformation Factory used to build transformations\n */\n constructor(initialOperationId, applyOperation, revertOperation, buildEmpty, buildTransformation) {\n this.applyOperation = applyOperation;\n this.revertOperation = revertOperation;\n this.buildEmpty = buildEmpty;\n this.buildTransformation = buildTransformation;\n this.HEAD_BRANCH = new Branch(this.buildTransformation);\n this.tree = new Tree(buildTransformation, this.HEAD_BRANCH);\n const initial = new Operation(initialOperationId, buildEmpty(initialOperationId));\n this.tree.insertOperationLast(this.HEAD_BRANCH, initial);\n this.HEAD_OPERATION = initial;\n }\n /**\n * Return the operation identified by its id.\n */\n get(operationId) {\n return this.tree.findOperation(this.HEAD_BRANCH, operationId).operation.data;\n }\n /**\n * Append a new operation as the last one\n */\n append(operationId, data) {\n const operation = new Operation(operationId, data);\n const branch = this.tree.getLastBranch();\n this.tree.insertOperationLast(branch, operation);\n this.HEAD_BRANCH = branch;\n this.HEAD_OPERATION = operation;\n }\n /**\n * Insert a new operation after a specific operation (may not be the last operation).\n * Following operations will be transformed according\n * to the new operation.\n */\n insert(operationId, data, insertAfter) {\n const operation = new Operation(operationId, data);\n this.revertTo(insertAfter);\n this.tree.insertOperationAfter(this.HEAD_BRANCH, operation, insertAfter);\n this.fastForward();\n }\n /**\n * @param operationId operation to undo\n * @param undoId the id of the \"undo operation\"\n * @param insertAfter the id of the operation after which to insert the undo\n */\n undo(operationId, undoId, insertAfter) {\n const { branch, operation } = this.tree.findOperation(this.HEAD_BRANCH, operationId);\n this.revertBefore(operationId);\n this.tree.undo(branch, operation);\n this.fastForward();\n this.insert(undoId, this.buildEmpty(undoId), insertAfter);\n }\n /**\n * @param operationId operation to redo\n * @param redoId the if of the \"redo operation\"\n * @param insertAfter the id of the operation after which to insert the redo\n */\n redo(operationId, redoId, insertAfter) {\n const { branch } = this.tree.findOperation(this.HEAD_BRANCH, operationId);\n this.revertBefore(operationId);\n this.tree.redo(branch);\n this.fastForward();\n this.insert(redoId, this.buildEmpty(redoId), insertAfter);\n }\n drop(operationId) {\n this.revertBefore(operationId);\n this.tree.drop(operationId);\n }\n /**\n * Revert the state as it was *before* the given operation was executed.\n */\n revertBefore(operationId) {\n const execution = this.tree.revertedExecution(this.HEAD_BRANCH).stopWith(operationId);\n this.revert(execution);\n }\n /**\n * Revert the state as it was *after* the given operation was executed.\n */\n revertTo(operationId) {\n const execution = operationId\n ? this.tree.revertedExecution(this.HEAD_BRANCH).stopBefore(operationId)\n : this.tree.revertedExecution(this.HEAD_BRANCH);\n this.revert(execution);\n }\n /**\n * Revert an execution\n */\n revert(execution) {\n for (const { next, operation, isCancelled } of execution) {\n if (!isCancelled) {\n this.revertOperation(operation.data);\n }\n if (next) {\n this.HEAD_BRANCH = next.branch;\n this.HEAD_OPERATION = next.operation;\n }\n }\n }\n /**\n * Replay the operations between the current HEAD_BRANCH and the end of the tree\n */\n fastForward() {\n const operations = this.HEAD_OPERATION\n ? this.tree.execution(this.HEAD_BRANCH).startAfter(this.HEAD_OPERATION.id)\n : this.tree.execution(this.HEAD_BRANCH);\n for (const { operation: operation, branch, isCancelled } of operations) {\n if (!isCancelled) {\n this.applyOperation(operation.data);\n }\n this.HEAD_OPERATION = operation;\n this.HEAD_BRANCH = branch;\n }\n }\n }\n\n function buildRevisionLog(initialRevisionId, recordChanges, dispatch) {\n return new SelectiveHistory(initialRevisionId, (revision) => {\n const commands = revision.commands.slice();\n const { changes } = recordChanges(() => {\n for (const command of commands) {\n dispatch(command);\n }\n });\n revision.setChanges(changes);\n }, (revision) => revertChanges([revision]), (id) => new Revision(id, \"empty\", [], []), {\n with: (revision) => (toTransform) => {\n return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands));\n },\n without: (revision) => (toTransform) => {\n return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands.map(inverseCommand).flat()));\n },\n });\n }\n /**\n * Revert changes from the given revisions\n */\n function revertChanges(revisions) {\n for (const revision of revisions.slice().reverse()) {\n for (let i = revision.changes.length - 1; i >= 0; i--) {\n const change = revision.changes[i];\n applyChange(change, \"before\");\n }\n }\n }\n /**\n * Apply the changes of the given HistoryChange to the state\n */\n function applyChange(change, target) {\n let val = change.root;\n let key = change.path[change.path.length - 1];\n for (let pathIndex = 0; pathIndex < change.path.slice(0, -1).length; pathIndex++) {\n const p = change.path[pathIndex];\n if (val[p] === undefined) {\n const nextPath = change.path[pathIndex + 1];\n val[p] = createEmptyStructure(nextPath);\n }\n val = val[p];\n }\n if (change[target] === undefined) {\n delete val[key];\n }\n else {\n val[key] = change[target];\n }\n }\n\n /**\n * Local History\n *\n * The local history is responsible of tracking the locally state updates\n * It maintains the local undo and redo stack to allow to undo/redo only local\n * changes\n */\n class LocalHistory extends owl.EventBus {\n constructor(dispatch, session) {\n super();\n this.dispatch = dispatch;\n this.session = session;\n /**\n * Ids of the revisions which can be undone\n */\n this.undoStack = [];\n /**\n * Ids of the revisions which can be redone\n */\n this.redoStack = [];\n this.session.on(\"new-local-state-update\", this, this.onNewLocalStateUpdate);\n this.session.on(\"revision-undone\", this, ({ commands }) => this.selectiveUndo(commands));\n this.session.on(\"revision-redone\", this, ({ commands }) => this.selectiveRedo(commands));\n this.session.on(\"pending-revisions-dropped\", this, ({ revisionIds }) => this.drop(revisionIds));\n this.session.on(\"snapshot\", this, () => {\n this.undoStack = [];\n this.redoStack = [];\n });\n }\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"REQUEST_UNDO\":\n if (!this.canUndo()) {\n return 6 /* CommandResult.EmptyUndoStack */;\n }\n break;\n case \"REQUEST_REDO\":\n if (!this.canRedo()) {\n return 7 /* CommandResult.EmptyRedoStack */;\n }\n break;\n }\n return 0 /* CommandResult.Success */;\n }\n beforeHandle(cmd) { }\n handle(cmd) {\n switch (cmd.type) {\n case \"REQUEST_UNDO\":\n case \"REQUEST_REDO\":\n // History changes (undo & redo) are *not* applied optimistically on the local state.\n // We wait a global confirmation from the server. The goal is to avoid handling concurrent\n // history changes on multiple clients which are very hard to manage correctly.\n this.requestHistoryChange(cmd.type === \"REQUEST_UNDO\" ? \"UNDO\" : \"REDO\");\n }\n }\n finalize() { }\n requestHistoryChange(type) {\n const id = type === \"UNDO\" ? this.undoStack.pop() : this.redoStack.pop();\n if (!id) {\n return;\n }\n if (type === \"UNDO\") {\n this.session.undo(id);\n this.redoStack.push(id);\n }\n else {\n this.session.redo(id);\n this.undoStack.push(id);\n }\n }\n canUndo() {\n return this.undoStack.length > 0;\n }\n canRedo() {\n return this.redoStack.length > 0;\n }\n drop(revisionIds) {\n this.undoStack = this.undoStack.filter((id) => !revisionIds.includes(id));\n this.redoStack = [];\n }\n onNewLocalStateUpdate({ id }) {\n this.undoStack.push(id);\n this.redoStack = [];\n if (this.undoStack.length > MAX_HISTORY_STEPS) {\n this.undoStack.shift();\n }\n }\n selectiveUndo(commands) {\n this.dispatch(\"UNDO\", { commands });\n }\n selectiveRedo(commands) {\n this.dispatch(\"REDO\", { commands });\n }\n }\n\n /**\n * Stateless sequence of events that can be processed by consumers.\n *\n * There are three kind of consumers:\n * - the main consumer\n * - the default consumer\n * - observer consumers\n *\n * Main consumer\n * -------------\n * Anyone can capture the event stream and become the main consumer.\n * If there is already a main consumer, it is kicked off and it will no longer\n * receive events.\n * The main consumer can release the stream at any moment to stop listening\n * events.\n *\n * Default consumer\n * ----------------\n * When the main consumer releases the stream and until the stream is captured\n * again, all events are transmitted to the default consumer.\n *\n * Observer consumers\n * ------------------\n * Observers permanently receive events.\n *\n */\n class EventStream {\n constructor() {\n this.observers = [];\n }\n registerAsDefault(owner, callbacks) {\n this.defaultSubscription = { owner, callbacks };\n if (!this.mainSubscription) {\n this.mainSubscription = this.defaultSubscription;\n }\n }\n /**\n * Register callbacks to observe the stream\n */\n observe(owner, callbacks) {\n this.observers.push({ owner, callbacks });\n }\n /**\n * Capture the stream for yourself\n */\n capture(owner, callbacks) {\n var _a, _b, _c;\n if (this.observers.find((sub) => sub.owner === owner)) {\n throw new Error(\"You are already subscribed forever\");\n }\n if ((_a = this.mainSubscription) === null || _a === void 0 ? void 0 : _a.owner) {\n (_c = (_b = this.mainSubscription.callbacks).release) === null || _c === void 0 ? void 0 : _c.call(_b);\n }\n this.mainSubscription = { owner, callbacks };\n }\n release(owner) {\n var _a, _b, _c, _d;\n if (((_a = this.mainSubscription) === null || _a === void 0 ? void 0 : _a.owner) !== owner ||\n this.observers.find((sub) => sub.owner === owner)) {\n return;\n }\n (_d = (_b = this.mainSubscription) === null || _b === void 0 ? void 0 : (_c = _b.callbacks).release) === null || _d === void 0 ? void 0 : _d.call(_c);\n this.mainSubscription = this.defaultSubscription;\n }\n /**\n * Release whichever subscription in charge and get back to the default subscription\n */\n getBackToDefault() {\n var _a, _b, _c;\n if (this.mainSubscription === this.defaultSubscription) {\n return;\n }\n (_c = (_a = this.mainSubscription) === null || _a === void 0 ? void 0 : (_b = _a.callbacks).release) === null || _c === void 0 ? void 0 : _c.call(_b);\n this.mainSubscription = this.defaultSubscription;\n }\n /**\n * Check if you are currently the main stream consumer\n */\n isListening(owner) {\n var _a;\n return ((_a = this.mainSubscription) === null || _a === void 0 ? void 0 : _a.owner) === owner;\n }\n /**\n * Push an event to the stream and broadcast it to consumers\n */\n send(event) {\n var _a;\n (_a = this.mainSubscription) === null || _a === void 0 ? void 0 : _a.callbacks.handleEvent(event);\n [...this.observers].forEach((sub) => sub.callbacks.handleEvent(event));\n }\n }\n\n /**\n * Processes all selection updates (usually from user inputs) and emits an event\n * with the new selected anchor\n */\n class SelectionStreamProcessor {\n constructor(getters) {\n this.getters = getters;\n this.stream = new EventStream();\n this.anchor = { cell: { col: 0, row: 0 }, zone: positionToZone({ col: 0, row: 0 }) };\n this.defaultAnchor = this.anchor;\n }\n capture(owner, anchor, callbacks) {\n this.stream.capture(owner, callbacks);\n this.anchor = anchor;\n }\n /**\n * Register as default subscriber and capture the event stream.\n */\n registerAsDefault(owner, anchor, callbacks) {\n this.checkAnchorZoneOrThrow(anchor);\n this.stream.registerAsDefault(owner, callbacks);\n this.defaultAnchor = anchor;\n this.capture(owner, anchor, callbacks);\n }\n resetDefaultAnchor(owner, anchor) {\n this.checkAnchorZoneOrThrow(anchor);\n if (this.stream.isListening(owner)) {\n this.anchor = anchor;\n }\n this.defaultAnchor = anchor;\n }\n resetAnchor(owner, anchor) {\n this.checkAnchorZoneOrThrow(anchor);\n if (this.stream.isListening(owner)) {\n this.anchor = anchor;\n }\n }\n observe(owner, callbacks) {\n this.stream.observe(owner, callbacks);\n }\n release(owner) {\n if (this.stream.isListening(owner)) {\n this.stream.release(owner);\n this.anchor = this.defaultAnchor;\n }\n }\n getBackToDefault() {\n this.stream.getBackToDefault();\n }\n /**\n * Select a new anchor\n */\n selectZone(anchor, mode = \"overrideSelection\") {\n const sheetId = this.getters.getActiveSheetId();\n anchor = {\n ...anchor,\n zone: this.getters.expandZone(sheetId, anchor.zone),\n };\n return this.processEvent({\n type: \"ZonesSelected\",\n anchor,\n mode,\n });\n }\n /**\n * Select a single cell as the new anchor.\n */\n selectCell(col, row) {\n const zone = positionToZone({ col, row });\n return this.selectZone({ zone, cell: { col, row } });\n }\n /**\n * Set the selection to one of the cells adjacent to the current anchor cell.\n */\n moveAnchorCell(direction, step = 1) {\n if (step !== \"end\" && step <= 0) {\n return new DispatchResult(83 /* CommandResult.InvalidSelectionStep */);\n }\n const { col, row } = this.getNextAvailablePosition(direction, step);\n return this.selectCell(col, row);\n }\n /**\n * Update the current anchor such that it includes the given\n * cell position.\n */\n setAnchorCorner(col, row) {\n const sheetId = this.getters.getActiveSheetId();\n const { col: anchorCol, row: anchorRow } = this.anchor.cell;\n const zone = {\n left: Math.min(anchorCol, col),\n top: Math.min(anchorRow, row),\n right: Math.max(anchorCol, col),\n bottom: Math.max(anchorRow, row),\n };\n const expandedZone = this.getters.expandZone(sheetId, zone);\n const anchor = { zone: expandedZone, cell: { col: anchorCol, row: anchorRow } };\n return this.processEvent({\n type: \"AlterZoneCorner\",\n mode: \"updateAnchor\",\n anchor: anchor,\n });\n }\n /**\n * Add a new cell to the current selection\n */\n addCellToSelection(col, row) {\n const sheetId = this.getters.getActiveSheetId();\n ({ col, row } = this.getters.getMainCellPosition({ sheetId, col, row }));\n const zone = this.getters.expandZone(sheetId, positionToZone({ col, row }));\n return this.processEvent({\n type: \"ZonesSelected\",\n anchor: { zone, cell: { col, row } },\n mode: \"newAnchor\",\n });\n }\n /**\n * Increase or decrease the size of the current anchor zone.\n * The anchor cell remains where it is. It's the opposite side\n * of the anchor zone which moves.\n */\n resizeAnchorZone(direction, step = 1) {\n if (step !== \"end\" && step <= 0) {\n return new DispatchResult(83 /* CommandResult.InvalidSelectionStep */);\n }\n const sheetId = this.getters.getActiveSheetId();\n const anchor = this.anchor;\n const { col: anchorCol, row: anchorRow } = anchor.cell;\n const { left, right, top, bottom } = anchor.zone;\n const starting = this.getStartingPosition(direction);\n let [deltaCol, deltaRow] = this.deltaToTarget(starting, direction, step);\n if (deltaCol === 0 && deltaRow === 0) {\n return DispatchResult.Success;\n }\n let result = anchor.zone;\n const expand = (z) => {\n z = organizeZone(z);\n const { left, right, top, bottom } = this.getters.expandZone(sheetId, z);\n return {\n left: Math.max(0, left),\n right: Math.min(this.getters.getNumberCols(sheetId) - 1, right),\n top: Math.max(0, top),\n bottom: Math.min(this.getters.getNumberRows(sheetId) - 1, bottom),\n };\n };\n const { col: refCol, row: refRow } = this.getReferencePosition();\n // check if we can shrink selection\n let n = 0;\n while (result !== null) {\n n++;\n if (deltaCol < 0) {\n const newRight = this.getNextAvailableCol(deltaCol, right - (n - 1), refRow);\n result = refCol <= right - n ? expand({ top, left, bottom, right: newRight }) : null;\n }\n if (deltaCol > 0) {\n const newLeft = this.getNextAvailableCol(deltaCol, left + (n - 1), refRow);\n result = left + n <= refCol ? expand({ top, left: newLeft, bottom, right }) : null;\n }\n if (deltaRow < 0) {\n const newBottom = this.getNextAvailableRow(deltaRow, refCol, bottom - (n - 1));\n result = refRow <= bottom - n ? expand({ top, left, bottom: newBottom, right }) : null;\n }\n if (deltaRow > 0) {\n const newTop = this.getNextAvailableRow(deltaRow, refCol, top + (n - 1));\n result = top + n <= refRow ? expand({ top: newTop, left, bottom, right }) : null;\n }\n result = result ? organizeZone(result) : result;\n if (result && !isEqual(result, anchor.zone)) {\n return this.processEvent({\n type: \"ZonesSelected\",\n mode: \"updateAnchor\",\n anchor: { zone: result, cell: { col: anchorCol, row: anchorRow } },\n });\n }\n }\n const currentZone = {\n top: anchorRow,\n bottom: anchorRow,\n left: anchorCol,\n right: anchorCol,\n };\n const zoneWithDelta = organizeZone({\n top: this.getNextAvailableRow(deltaRow, refCol, top),\n left: this.getNextAvailableCol(deltaCol, left, refRow),\n bottom: this.getNextAvailableRow(deltaRow, refCol, bottom),\n right: this.getNextAvailableCol(deltaCol, right, refRow),\n });\n result = expand(union(currentZone, zoneWithDelta));\n const newAnchor = { zone: result, cell: { col: anchorCol, row: anchorRow } };\n return this.processEvent({\n type: \"ZonesSelected\",\n anchor: newAnchor,\n mode: \"updateAnchor\",\n });\n }\n selectColumn(index, mode) {\n const sheetId = this.getters.getActiveSheetId();\n const bottom = this.getters.getNumberRows(sheetId) - 1;\n let zone = { left: index, right: index, top: 0, bottom };\n const top = this.getters.findFirstVisibleColRowIndex(sheetId, \"ROW\");\n let col, row;\n switch (mode) {\n case \"overrideSelection\":\n case \"newAnchor\":\n col = index;\n row = top;\n break;\n case \"updateAnchor\":\n ({ col, row } = this.anchor.cell);\n zone = union(zone, { left: col, right: col, top, bottom });\n break;\n }\n return this.processEvent({\n type: \"HeadersSelected\",\n anchor: { zone, cell: { col, row } },\n mode,\n });\n }\n selectRow(index, mode) {\n const sheetId = this.getters.getActiveSheetId();\n const right = this.getters.getNumberCols(sheetId) - 1;\n let zone = { top: index, bottom: index, left: 0, right };\n const left = this.getters.findFirstVisibleColRowIndex(sheetId, \"COL\");\n let col, row;\n switch (mode) {\n case \"overrideSelection\":\n case \"newAnchor\":\n col = left;\n row = index;\n break;\n case \"updateAnchor\":\n ({ col, row } = this.anchor.cell);\n zone = union(zone, { left, right, top: row, bottom: row });\n break;\n }\n return this.processEvent({\n type: \"HeadersSelected\",\n anchor: { zone, cell: { col, row } },\n mode,\n });\n }\n /**\n * Loop the current selection while keeping the same anchor. The selection will loop through:\n * 1) the smallest zone that contain the anchor and that have only empty cells bordering it\n * 2) the whole sheet\n * 3) the anchor cell\n */\n loopSelection() {\n const sheetId = this.getters.getActiveSheetId();\n const anchor = this.anchor;\n // The whole sheet is selected, select the anchor cell\n if (isEqual(this.anchor.zone, this.getters.getSheetZone(sheetId))) {\n return this.selectZone({ ...anchor, zone: positionToZone(anchor.cell) });\n }\n const tableZone = this.expandZoneToTable(anchor.zone);\n return !deepEquals(tableZone, anchor.zone)\n ? this.selectZone({ ...anchor, zone: tableZone })\n : this.selectAll();\n }\n /**\n * Select a \"table\" around the current selection.\n * We define a table by the smallest zone that contain the anchor and that have only empty\n * cells bordering it\n */\n selectTableAroundSelection() {\n const tableZone = this.expandZoneToTable(this.anchor.zone);\n return this.selectZone({ ...this.anchor, zone: tableZone }, \"updateAnchor\");\n }\n /**\n * Select the entire sheet\n */\n selectAll() {\n const sheetId = this.getters.getActiveSheetId();\n const bottom = this.getters.getNumberRows(sheetId) - 1;\n const right = this.getters.getNumberCols(sheetId) - 1;\n const zone = { left: 0, top: 0, bottom, right };\n return this.processEvent({\n type: \"HeadersSelected\",\n mode: \"overrideSelection\",\n anchor: { zone, cell: this.anchor.cell },\n });\n }\n /**\n * Process a new anchor selection event. If the new anchor is inside\n * the sheet boundaries, the event is pushed to the event stream to\n * be processed.\n */\n processEvent(newAnchorEvent) {\n const event = { ...newAnchorEvent, previousAnchor: deepCopy(this.anchor) };\n const commandResult = this.checkEventAnchorZone(event);\n if (commandResult !== 0 /* CommandResult.Success */) {\n return new DispatchResult(commandResult);\n }\n this.anchor = event.anchor;\n this.stream.send(event);\n return DispatchResult.Success;\n }\n checkEventAnchorZone(event) {\n return this.checkAnchorZone(event.anchor);\n }\n checkAnchorZone(anchor) {\n const { cell, zone } = anchor;\n if (!isInside(cell.col, cell.row, zone)) {\n return 16 /* CommandResult.InvalidAnchorZone */;\n }\n const { left, right, top, bottom } = zone;\n const sheetId = this.getters.getActiveSheetId();\n const refCol = this.getters.findVisibleHeader(sheetId, \"COL\", range(left, right + 1));\n const refRow = this.getters.findVisibleHeader(sheetId, \"ROW\", range(top, bottom + 1));\n if (refRow === undefined || refCol === undefined) {\n return 17 /* CommandResult.SelectionOutOfBound */;\n }\n return 0 /* CommandResult.Success */;\n }\n checkAnchorZoneOrThrow(anchor) {\n const result = this.checkAnchorZone(anchor);\n if (result === 16 /* CommandResult.InvalidAnchorZone */) {\n throw new Error(_t(\"The provided anchor is invalid. The cell must be part of the zone.\"));\n }\n }\n /**\n * ---- PRIVATE ----\n */\n /** Computes the next cell position in the direction of deltaX and deltaY\n * by crossing through merges and skipping hidden cells.\n * Note that the resulting position might be out of the sheet, it needs to be validated.\n */\n getNextAvailablePosition(direction, step = 1) {\n const { col, row } = this.anchor.cell;\n const delta = this.deltaToTarget({ col, row }, direction, step);\n return {\n col: this.getNextAvailableCol(delta[0], col, row),\n row: this.getNextAvailableRow(delta[1], col, row),\n };\n }\n getNextAvailableCol(delta, colIndex, rowIndex) {\n const sheetId = this.getters.getActiveSheetId();\n const position = { col: colIndex, row: rowIndex };\n const isInPositionMerge = (nextCol) => this.getters.isInSameMerge(sheetId, colIndex, rowIndex, nextCol, rowIndex);\n return this.getNextAvailableHeader(delta, \"COL\", colIndex, position, isInPositionMerge);\n }\n getNextAvailableRow(delta, colIndex, rowIndex) {\n const sheetId = this.getters.getActiveSheetId();\n const position = { col: colIndex, row: rowIndex };\n const isInPositionMerge = (nextRow) => this.getters.isInSameMerge(sheetId, colIndex, rowIndex, colIndex, nextRow);\n return this.getNextAvailableHeader(delta, \"ROW\", rowIndex, position, isInPositionMerge);\n }\n getNextAvailableHeader(delta, dimension, startingHeaderIndex, position, isInPositionMerge) {\n const sheetId = this.getters.getActiveSheetId();\n if (delta === 0) {\n return startingHeaderIndex;\n }\n const step = Math.sign(delta);\n let header = startingHeaderIndex + delta;\n while (isInPositionMerge(header)) {\n header += step;\n }\n while (this.getters.isHeaderHidden(sheetId, dimension, header)) {\n header += step;\n }\n const outOfBound = header < 0 || header > this.getters.getNumberHeaders(sheetId, dimension) - 1;\n if (outOfBound) {\n if (this.getters.isHeaderHidden(sheetId, dimension, startingHeaderIndex)) {\n return this.getNextAvailableHeader(-step, dimension, startingHeaderIndex, position, isInPositionMerge);\n }\n else {\n return startingHeaderIndex;\n }\n }\n return header;\n }\n /**\n * Finds a visible cell in the currently selected zone starting with the anchor.\n * If the anchor is hidden, browses from left to right and top to bottom to\n * find a visible cell.\n */\n getReferencePosition() {\n const sheetId = this.getters.getActiveSheetId();\n const anchor = this.anchor;\n const { left, right, top, bottom } = anchor.zone;\n const { col: anchorCol, row: anchorRow } = anchor.cell;\n return {\n col: this.getters.isColHidden(sheetId, anchorCol)\n ? this.getters.findVisibleHeader(sheetId, \"COL\", range(left, right + 1)) || anchorCol\n : anchorCol,\n row: this.getters.isRowHidden(sheetId, anchorRow)\n ? this.getters.findVisibleHeader(sheetId, \"ROW\", range(top, bottom + 1)) || anchorRow\n : anchorRow,\n };\n }\n deltaToTarget(position, direction, step) {\n switch (direction) {\n case \"up\":\n return step !== \"end\"\n ? [0, -step]\n : [0, this.getEndOfCluster(position, \"rows\", -1) - position.row];\n case \"down\":\n return step !== \"end\"\n ? [0, step]\n : [0, this.getEndOfCluster(position, \"rows\", 1) - position.row];\n case \"left\":\n return step !== \"end\"\n ? [-step, 0]\n : [this.getEndOfCluster(position, \"cols\", -1) - position.col, 0];\n case \"right\":\n return step !== \"end\"\n ? [step, 0]\n : [this.getEndOfCluster(position, \"cols\", 1) - position.col, 0];\n }\n }\n // TODO rename this\n getStartingPosition(direction) {\n let { col, row } = this.getPosition();\n const zone = this.anchor.zone;\n switch (direction) {\n case \"down\":\n case \"up\":\n row = row === zone.top ? zone.bottom : zone.top;\n break;\n case \"left\":\n case \"right\":\n col = col === zone.right ? zone.left : zone.right;\n break;\n }\n return { col, row };\n }\n /**\n * Given a starting position, compute the end of the cluster containing the position in the given\n * direction or the start of the next cluster. We define cluster here as side-by-side cells that\n * all have a content.\n *\n * We will return the end of the cluster if the given cell is inside a cluster, and the start of the\n * next cluster if the given cell is outside a cluster or at the border of a cluster in the given direction.\n */\n getEndOfCluster(startPosition, dim, dir) {\n const sheet = this.getters.getActiveSheet();\n let currentPosition = startPosition;\n // If both the current cell and the next cell are not empty, we want to go to the end of the cluster\n const nextCellPosition = this.getNextCellPosition(startPosition, dim, dir);\n let mode = !this.isCellEmpty(currentPosition, sheet.id) && !this.isCellEmpty(nextCellPosition, sheet.id)\n ? \"endOfCluster\"\n : \"nextCluster\";\n while (true) {\n const nextCellPosition = this.getNextCellPosition(currentPosition, dim, dir);\n // Break if nextPosition == currentPosition, which happens if there's no next valid position\n if (currentPosition.col === nextCellPosition.col &&\n currentPosition.row === nextCellPosition.row) {\n break;\n }\n const isNextCellEmpty = this.isCellEmpty(nextCellPosition, sheet.id);\n if (mode === \"endOfCluster\" && isNextCellEmpty) {\n break;\n }\n else if (mode === \"nextCluster\" && !isNextCellEmpty) {\n // We want to return the start of the next cluster, not the end of the empty zone\n currentPosition = nextCellPosition;\n break;\n }\n currentPosition = nextCellPosition;\n }\n return dim === \"cols\" ? currentPosition.col : currentPosition.row;\n }\n /**\n * Check if a cell is empty or undefined in the model. If the cell is part of a merge,\n * check if the merge containing the cell is empty.\n */\n isCellEmpty({ col, row }, sheetId = this.getters.getActiveSheetId()) {\n const position = this.getters.getMainCellPosition({ sheetId, col, row });\n const cell = this.getters.getEvaluatedCell(position);\n return cell.type === CellValueType.empty;\n }\n /** Computes the next cell position in the given direction by crossing through merges and skipping hidden cells.\n *\n * This has the same behaviour as getNextAvailablePosition() for certain arguments, but use this method instead\n * inside directionToDelta(), which is called in getNextAvailablePosition(), to avoid possible infinite\n * recursion.\n */\n getNextCellPosition(currentPosition, dimension, direction) {\n const dimOfInterest = dimension === \"cols\" ? \"col\" : \"row\";\n const startingPosition = { ...currentPosition };\n const nextCoord = dimension === \"cols\"\n ? this.getNextAvailableCol(direction, startingPosition.col, startingPosition.row)\n : this.getNextAvailableRow(direction, startingPosition.col, startingPosition.row);\n startingPosition[dimOfInterest] = nextCoord;\n return { col: startingPosition.col, row: startingPosition.row };\n }\n getPosition() {\n return { ...this.anchor.cell };\n }\n /**\n * Expand the given zone to a table.\n * We define a table by the smallest zone that contain the anchor and that have only empty\n * cells bordering it\n */\n expandZoneToTable(zoneToExpand) {\n /** Try to expand the zone by one col/row in any direction to include a new non-empty cell */\n const expandZone = (zone) => {\n for (const col of range(zone.left, zone.right + 1)) {\n if (!this.isCellEmpty({ col, row: zone.top - 1 })) {\n return { ...zone, top: zone.top - 1 };\n }\n if (!this.isCellEmpty({ col, row: zone.bottom + 1 })) {\n return { ...zone, bottom: zone.bottom + 1 };\n }\n }\n for (const row of range(zone.top, zone.bottom + 1)) {\n if (!this.isCellEmpty({ col: zone.left - 1, row })) {\n return { ...zone, left: zone.left - 1 };\n }\n if (!this.isCellEmpty({ col: zone.right + 1, row })) {\n return { ...zone, right: zone.right + 1 };\n }\n }\n return zone;\n };\n let hasExpanded = false;\n let zone = zoneToExpand;\n do {\n hasExpanded = false;\n const newZone = expandZone(zone);\n if (!isEqual(zone, newZone)) {\n hasExpanded = true;\n zone = newZone;\n continue;\n }\n } while (hasExpanded);\n return zone;\n }\n }\n\n class StateObserver {\n constructor() {\n this.changes = [];\n this.commands = [];\n }\n /**\n * Record the changes which could happen in the given callback, save them in a\n * new revision with the given id and userId.\n */\n recordChanges(callback) {\n this.changes = [];\n this.commands = [];\n callback();\n return { changes: this.changes, commands: this.commands };\n }\n addCommand(command) {\n this.commands.push(command);\n }\n addChange(...args) {\n const val = args.pop();\n const [root, ...path] = args;\n let value = root;\n let key = path[path.length - 1];\n for (let pathIndex = 0; pathIndex <= path.length - 2; pathIndex++) {\n const p = path[pathIndex];\n if (value[p] === undefined) {\n const nextPath = path[pathIndex + 1];\n value[p] = createEmptyStructure(nextPath);\n }\n value = value[p];\n }\n if (value[key] === val) {\n return;\n }\n this.changes.push({\n root,\n path,\n before: value[key],\n after: val,\n });\n if (val === undefined) {\n delete value[key];\n }\n else {\n value[key] = val;\n }\n }\n }\n\n /**\n * Each axis present inside a graph needs to be identified by an unsigned integer\n * The value does not matter, it can be hardcoded.\n */\n const catAxId = 17781237;\n const valAxId = 88853993;\n function createChart(chart, chartSheetIndex, data) {\n const namespaces = [\n [\"xmlns:r\", RELATIONSHIP_NSR],\n [\"xmlns:a\", DRAWING_NS_A],\n [\"xmlns:c\", DRAWING_NS_C],\n ];\n const chartShapeProperty = shapeProperty({\n backgroundColor: chart.data.backgroundColor,\n line: { color: \"000000\" },\n });\n // to manually position the chart in the figure container\n let title = escapeXml ``;\n if (chart.data.title) {\n title = escapeXml /*xml*/ `\n \n ${insertText(chart.data.title, chart.data.fontColor)}\n \n \n `;\n }\n // switch on chart type\n let plot = escapeXml ``;\n switch (chart.data.type) {\n case \"bar\":\n plot = addBarChart(chart.data);\n break;\n case \"line\":\n plot = addLineChart(chart.data);\n break;\n case \"pie\":\n plot = addDoughnutChart(chart.data, chartSheetIndex, data, { holeSize: 0 });\n break;\n }\n let position = \"t\";\n switch (chart.data.legendPosition) {\n case \"bottom\":\n position = \"b\";\n break;\n case \"left\":\n position = \"l\";\n break;\n case \"right\":\n position = \"r\";\n break;\n case \"top\":\n position = \"t\";\n break;\n }\n const fontColor = chart.data.fontColor;\n const xml = escapeXml /*xml*/ `\n \n \n \n ${chartShapeProperty}\n \n ${title}\n \n \n \n \n ${plot}\n ${shapeProperty({ backgroundColor: chart.data.backgroundColor })}\n \n ${addLegend(position, fontColor)}\n \n \n `;\n return parseXML(xml);\n }\n function shapeProperty(params) {\n return escapeXml /*xml*/ `\n \n ${params.backgroundColor ? solidFill(params.backgroundColor) : \"\"}\n ${params.line ? lineAttributes(params.line) : \"\"}\n \n `;\n }\n function solidFill(color) {\n return escapeXml /*xml*/ `\n \n \n \n `;\n }\n function lineAttributes(params) {\n const attrs = [[\"cmpd\", \"sng\"]];\n if (params.width) {\n attrs.push([\"w\", convertDotValueToEMU(params.width)]);\n }\n const lineStyle = params.style ? escapeXml /*xml*/ `` : \"\";\n return escapeXml /*xml*/ `\n \n ${solidFill(params.color)}\n ${lineStyle}\n \n `;\n }\n function insertText(text, fontColor = \"000000\", fontsize = 22) {\n return escapeXml /*xml*/ `\n \n \n \n \n \n \n \n ${solidFill(fontColor)}\n \n \n \n \n \n ${text}\n \n \n \n \n `;\n }\n function insertTextProperties(fontsize = 12, fontColor = \"000000\", bold = false, italic = false) {\n const defPropertiesAttributes = [\n [\"b\", bold ? \"1\" : \"0\"],\n [\"i\", italic ? \"1\" : \"0\"],\n [\"sz\", fontsize * 100],\n ];\n return escapeXml /*xml*/ `\n \n \n \n \n \n \n ${solidFill(fontColor)}\n \n \n \n \n \n `;\n }\n function addBarChart(chart) {\n // gapWitdh and overlap that define the space between clusters (in %) and the overlap between datasets (from -100: completely scattered to 100, completely overlapped)\n // see gapWidth : https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_gapWidth_topic_ID0EFVEQB.html#topic_ID0EFVEQB\n // see overlap : https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_overlap_topic_ID0ELYQQB.html#topic_ID0ELYQQB\n //\n // overlap and gapWitdh seems to be by default at -20 and 20 in chart.js.\n // See https://www.chartjs.org/docs/latest/charts/bar.html and https://www.chartjs.org/docs/latest/charts/bar.html#barpercentage-vs-categorypercentage\n const colors = new ChartColors();\n const dataSetsNodes = [];\n for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {\n const color = toXlsxHexColor(colors.next());\n const dataShapeProperty = shapeProperty({\n backgroundColor: color,\n line: { color },\n });\n dataSetsNodes.push(escapeXml /*xml*/ `\n \n \n \n ${dataset.label ? escapeXml /*xml*/ `${stringRef(dataset.label)}` : \"\"}\n ${dataShapeProperty}\n ${chart.labelRange ? escapeXml /*xml*/ `${stringRef(chart.labelRange)}` : \"\"} \n \n ${numberRef(dataset.range)}\n \n \n `);\n }\n // Excel does not support this feature\n const axisPos = chart.verticalAxisPosition === \"left\" ? \"l\" : \"r\";\n const grouping = chart.stacked ? \"stacked\" : \"clustered\";\n const overlap = chart.stacked ? 100 : -20;\n return escapeXml /*xml*/ `\n \n \n \n \n \n \n \n ${joinXmlNodes(dataSetsNodes)}\n \n \n \n ${addAx(\"b\", \"c:catAx\", catAxId, valAxId, { fontColor: chart.fontColor })}\n ${addAx(axisPos, \"c:valAx\", valAxId, catAxId, { fontColor: chart.fontColor })}\n `;\n }\n function addLineChart(chart) {\n const colors = new ChartColors();\n const dataSetsNodes = [];\n for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {\n const dataShapeProperty = shapeProperty({\n line: {\n width: 2.5,\n style: \"solid\",\n color: toXlsxHexColor(colors.next()),\n },\n });\n dataSetsNodes.push(escapeXml /*xml*/ `\n \n \n \n \n \n \n \n \n ${dataset.label ? escapeXml `${stringRef(dataset.label)}` : \"\"}\n ${dataShapeProperty}\n ${chart.labelRange ? escapeXml `${stringRef(chart.labelRange)}` : \"\"} \n \n ${numberRef(dataset.range)}\n \n \n `);\n }\n // Excel does not support this feature\n const axisPos = chart.verticalAxisPosition === \"left\" ? \"l\" : \"r\";\n const grouping = chart.stacked ? \"stacked\" : \"standard\";\n return escapeXml /*xml*/ `\n \n \n \n \n ${joinXmlNodes(dataSetsNodes)}\n \n \n \n ${addAx(\"b\", \"c:catAx\", catAxId, valAxId, { fontColor: chart.fontColor })}\n ${addAx(axisPos, \"c:valAx\", valAxId, catAxId, { fontColor: chart.fontColor })}\n `;\n }\n function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSize: 50 }) {\n const colors = new ChartColors();\n const maxLength = Math.max(...chart.dataSets.map((ds) => getRangeSize(ds.range, chartSheetIndex, data)));\n const doughnutColors = range(0, maxLength).map(() => toXlsxHexColor(colors.next()));\n const dataSetsNodes = [];\n for (const [dsIndex, dataset] of Object.entries(chart.dataSets).reverse()) {\n //dataset slice labels\n const dsSize = getRangeSize(dataset.range, chartSheetIndex, data);\n const dataPoints = [];\n for (const index of range(0, dsSize)) {\n const pointShapeProperty = shapeProperty({\n backgroundColor: doughnutColors[index],\n line: { color: \"FFFFFF\", width: 1.5 },\n });\n dataPoints.push(escapeXml /*xml*/ `\n \n \n ${pointShapeProperty}\n \n `);\n }\n dataSetsNodes.push(escapeXml /*xml*/ `\n \n \n \n ${dataset.label ? escapeXml `${stringRef(dataset.label)}` : \"\"}\n ${joinXmlNodes(dataPoints)}\n ${insertDataLabels({ showLeaderLines: true })}\n ${chart.labelRange ? escapeXml `${stringRef(chart.labelRange)}` : \"\"}\n \n ${numberRef(dataset.range)}\n \n \n `);\n }\n return escapeXml /*xml*/ `\n \n \n \n ${insertDataLabels()}\n ${joinXmlNodes(dataSetsNodes)}\n \n `;\n }\n function insertDataLabels({ showLeaderLines } = { showLeaderLines: false }) {\n return escapeXml /*xml*/ `\n \n \n \n \n \n \n \n \n \n `;\n }\n function addAx(position, axisName, axId, crossAxId, { fontColor }) {\n // Each Axis present inside a graph needs to be identified by an unsigned integer in order to be referenced by its crossAxis.\n // I.e. x-axis, will reference y-axis and vice-versa.\n return escapeXml /*xml*/ `\n <${axisName}>\n \n \n \n \n \n \n \n ${insertMajorGridLines()}\n \n \n \n \n ${insertText(\"\")}\n \n ${insertTextProperties(10, fontColor)}\n \n \n `;\n }\n function addLegend(position, fontColor) {\n return escapeXml /*xml*/ `\n \n \n \n ${insertTextProperties(10, fontColor)}\n \n `;\n }\n function insertMajorGridLines(color = \"B7B7B7\") {\n return escapeXml /*xml*/ `\n \n ${shapeProperty({ line: { color } })}\n \n `;\n }\n function stringRef(reference) {\n return escapeXml /*xml*/ `\n \n ${reference}\n \n `;\n }\n function numberRef(reference) {\n return escapeXml /*xml*/ `\n \n ${reference}\n \n \n `;\n }\n\n function addFormula(cell) {\n const formula = cell.content;\n const functions = functionRegistry.content;\n const tokens = tokenize(formula);\n const attrs = [];\n let node = escapeXml ``;\n const isExported = tokens\n .filter((tk) => tk.type === \"FUNCTION\")\n .every((tk) => functions[tk.value.toUpperCase()].isExported);\n if (isExported) {\n let cycle = escapeXml ``;\n const XlsxFormula = adaptFormulaToExcel(formula);\n // hack for cycles : if we don't set a value (be it 0 or #VALUE!), it will appear as invisible on excel,\n // Making it very hard for the client to find where the recursion is.\n if (cell.value === CellErrorType.CircularDependency) {\n attrs.push([\"t\", \"str\"]);\n cycle = escapeXml /*xml*/ `${cell.value}`;\n }\n node = escapeXml /*xml*/ `\n \n ${XlsxFormula}\n \n ${cycle}\n `;\n return { attrs, node };\n }\n else {\n // Shouldn't we always output the value then ?\n const value = cell.value;\n // what if value = 0? Is this condition correct?\n if (value) {\n const type = getCellType(value);\n attrs.push([\"t\", type]);\n node = escapeXml /*xml*/ `${value}`;\n }\n return { attrs, node };\n }\n }\n function addContent(content, sharedStrings, forceString = false) {\n let value = content;\n const attrs = [];\n if (!forceString && [\"TRUE\", \"FALSE\"].includes(value.trim())) {\n value = value === \"TRUE\" ? \"1\" : \"0\";\n attrs.push([\"t\", \"b\"]);\n }\n else if (forceString || !isNumber(value)) {\n const { id } = pushElement(content, sharedStrings);\n value = id.toString();\n attrs.push([\"t\", \"s\"]);\n }\n return { attrs, node: escapeXml /*xml*/ `${value}` };\n }\n function adaptFormulaToExcel(formulaText) {\n if (formulaText[0] === \"=\") {\n formulaText = formulaText.slice(1);\n }\n let ast;\n try {\n ast = parse(formulaText);\n }\n catch (error) {\n return formulaText;\n }\n ast = convertAstNodes(ast, \"STRING\", convertDateFormat);\n ast = convertAstNodes(ast, \"FUNCALL\", (ast) => {\n ast = { ...ast, value: ast.value.toUpperCase() };\n ast = prependNonRetrocompatibleFunction(ast);\n ast = addMissingRequiredArgs(ast);\n return ast;\n });\n return ast ? astToFormula(ast) : formulaText;\n }\n /**\n * Some Excel function need required args that might not be mandatory in o-spreadsheet.\n * This adds those missing args.\n */\n function addMissingRequiredArgs(ast) {\n const formulaName = ast.value.toUpperCase();\n const args = ast.args;\n const exportDefaultArgs = FORCE_DEFAULT_ARGS_FUNCTIONS[formulaName];\n if (exportDefaultArgs) {\n const requiredArgs = functionRegistry.content[formulaName].args.filter((el) => !el.optional);\n const diffArgs = requiredArgs.length - ast.args.length;\n if (diffArgs) {\n // We know that we have at least 1 default Value missing\n for (let i = ast.args.length; i < requiredArgs.length; i++) {\n const currentDefaultArg = exportDefaultArgs[i - diffArgs];\n args.push({ type: currentDefaultArg.type, value: currentDefaultArg.value });\n }\n }\n }\n return { ...ast, args };\n }\n /**\n * Prepend function names that are not compatible with Old Excel versions\n */\n function prependNonRetrocompatibleFunction(ast) {\n const formulaName = ast.value.toUpperCase();\n return {\n ...ast,\n value: NON_RETROCOMPATIBLE_FUNCTIONS.includes(formulaName)\n ? `_xlfn.${formulaName}`\n : formulaName,\n };\n }\n /**\n * Convert strings that correspond to a date to the format YYYY-DD-MM\n */\n function convertDateFormat(ast) {\n const value = ast.value.replace(new RegExp('\"', \"g\"), \"\");\n const internalDate = parseDateTime(value);\n if (internalDate) {\n let format = [];\n if (value.match(mdyDateRegexp) || value.match(ymdDateRegexp)) {\n format.push(\"yyyy-mm-dd\");\n }\n if (value.match(timeRegexp)) {\n format.push(\"hh:mm:ss\");\n }\n return {\n ...ast,\n value: formatValue(internalDate.value, format.join(\" \")),\n };\n }\n else {\n return { ...ast, value: ast.value.replace(/\\\\\"/g, `\"\"`) };\n }\n }\n\n function addConditionalFormatting(dxfs, conditionalFormats) {\n // Conditional Formats\n const cfNodes = [];\n for (const cf of conditionalFormats) {\n // Special case for each type of rule: might be better to extract that logic in dedicated functions\n switch (cf.rule.type) {\n case \"CellIsRule\":\n cfNodes.push(addCellIsRule(cf, cf.rule, dxfs));\n break;\n case \"ColorScaleRule\":\n cfNodes.push(addColorScaleRule(cf, cf.rule));\n break;\n case \"IconSetRule\":\n cfNodes.push(addIconSetRule(cf, cf.rule));\n break;\n default:\n // @ts-ignore Typescript knows it will never happen at compile time\n console.warn(`Conditional formatting ${cf.rule.type} not implemented`);\n break;\n }\n }\n return cfNodes;\n }\n // ----------------------\n // RULES\n // ----------------------\n function addCellIsRule(cf, rule, dxfs) {\n const ruleAttributes = commonCfAttributes(cf);\n const operator = convertOperator(rule.operator);\n ruleAttributes.push(...cellRuleTypeAttributes(rule), [\"operator\", operator]);\n const formulas = cellRuleFormula(cf.ranges, rule).map((formula) => escapeXml /*xml*/ `${formula}`);\n const dxf = {\n font: {\n color: { rgb: rule.style.textColor },\n bold: rule.style.bold,\n italic: rule.style.italic,\n strike: rule.style.strikethrough,\n underline: rule.style.underline,\n },\n };\n if (rule.style.fillColor) {\n dxf.fill = { fgColor: { rgb: rule.style.fillColor } };\n }\n const { id } = pushElement(dxf, dxfs);\n ruleAttributes.push([\"dxfId\", id]);\n return escapeXml /*xml*/ `\n \n \n ${joinXmlNodes(formulas)}\n \n \n `;\n }\n function cellRuleFormula(ranges, rule) {\n const firstCell = ranges[0].split(\":\")[0];\n const values = rule.values;\n switch (rule.operator) {\n case \"ContainsText\":\n return [`NOT(ISERROR(SEARCH(\"${values[0]}\",${firstCell})))`];\n case \"NotContains\":\n return [`ISERROR(SEARCH(\"${values[0]}\",${firstCell}))`];\n case \"BeginsWith\":\n return [`LEFT(${firstCell},LEN(\"${values[0]}\"))=\"${values[0]}\"`];\n case \"EndsWith\":\n return [`RIGHT(${firstCell},LEN(\"${values[0]}\"))=\"${values[0]}\"`];\n case \"IsEmpty\":\n return [`LEN(TRIM(${firstCell}))=0`];\n case \"IsNotEmpty\":\n return [`LEN(TRIM(${firstCell}))>0`];\n case \"Equal\":\n case \"NotEqual\":\n case \"GreaterThan\":\n case \"GreaterThanOrEqual\":\n case \"LessThan\":\n case \"LessThanOrEqual\":\n return [values[0]];\n case \"Between\":\n case \"NotBetween\":\n return [values[0], values[1]];\n }\n }\n function cellRuleTypeAttributes(rule) {\n const operator = convertOperator(rule.operator);\n switch (rule.operator) {\n case \"ContainsText\":\n case \"NotContains\":\n case \"BeginsWith\":\n case \"EndsWith\":\n return [\n [\"type\", operator],\n [\"text\", rule.values[0]],\n ];\n case \"IsEmpty\":\n case \"IsNotEmpty\":\n return [[\"type\", operator]];\n case \"Equal\":\n case \"NotEqual\":\n case \"GreaterThan\":\n case \"GreaterThanOrEqual\":\n case \"LessThan\":\n case \"LessThanOrEqual\":\n case \"Between\":\n case \"NotBetween\":\n return [[\"type\", \"cellIs\"]];\n }\n }\n function addColorScaleRule(cf, rule) {\n const ruleAttributes = commonCfAttributes(cf);\n ruleAttributes.push([\"type\", \"colorScale\"]);\n /** mimic our flow:\n * for a given ColorScale CF, each range of the \"ranges set\" has its own behaviour.\n */\n const conditionalFormats = [];\n for (const range of cf.ranges) {\n const cfValueObject = [];\n const colors = [];\n let canExport = true;\n for (let position of [\"minimum\", \"midpoint\", \"maximum\"]) {\n const threshold = rule[position];\n if (!threshold) {\n // pass midpoint if not defined\n continue;\n }\n if (threshold.type === \"formula\") {\n canExport = false;\n continue;\n }\n cfValueObject.push(thresholdAttributes(threshold, position));\n colors.push([[\"rgb\", toXlsxHexColor(colorNumberString(threshold.color))]]);\n }\n if (!canExport) {\n console.warn(\"Conditional formats with formula rules are not supported at the moment. The rule is therefore skipped.\");\n continue;\n }\n const cfValueObjectNodes = cfValueObject.map((attrs) => escapeXml /*xml*/ ``);\n const cfColorNodes = colors.map((attrs) => escapeXml /*xml*/ ``);\n conditionalFormats.push(escapeXml /*xml*/ `\n \n \n \n ${joinXmlNodes(cfValueObjectNodes)}\n ${joinXmlNodes(cfColorNodes)}\n \n \n \n `);\n }\n return joinXmlNodes(conditionalFormats);\n }\n function addIconSetRule(cf, rule) {\n const ruleAttributes = commonCfAttributes(cf);\n ruleAttributes.push([\"type\", \"iconSet\"]);\n /** mimic our flow:\n * for a given IconSet CF, each range of the \"ranges set\" has its own behaviour.\n */\n const conditionalFormats = [];\n for (const range of cf.ranges) {\n const cfValueObject = [\n // It looks like they always want 3 cfvo and they add a dummy entry\n [\n [\"type\", \"percent\"],\n [\"val\", 0],\n ],\n ];\n let canExport = true;\n for (let position of [\"lowerInflectionPoint\", \"upperInflectionPoint\"]) {\n if (rule[position].type === \"formula\") {\n canExport = false;\n continue;\n }\n const threshold = rule[position];\n cfValueObject.push([\n ...thresholdAttributes(threshold, position),\n [\"gte\", threshold.operator === \"ge\" ? \"1\" : \"0\"],\n ]);\n }\n if (!canExport) {\n console.warn(\"Conditional formats with formula rules are not supported at the moment. The rule is therefore skipped.\");\n continue;\n }\n const cfValueObjectNodes = cfValueObject.map((attrs) => escapeXml /*xml*/ ``);\n conditionalFormats.push(escapeXml /*xml*/ `\n \n \n \n ${joinXmlNodes(cfValueObjectNodes)}\n \n \n \n `);\n }\n return joinXmlNodes(conditionalFormats);\n }\n // ----------------------\n // MISC\n // ----------------------\n function commonCfAttributes(cf) {\n return [\n [\"priority\", 1],\n [\"stopIfTrue\", cf.stopIfTrue ? 1 : 0],\n ];\n }\n function getIconSet(iconSet) {\n return XLSX_ICONSET_MAP[Object.keys(XLSX_ICONSET_MAP).find((key) => iconSet.upper.toLowerCase().startsWith(key)) ||\n \"dots\"];\n }\n function thresholdAttributes(threshold, position) {\n const type = getExcelThresholdType(threshold.type, position);\n const attrs = [[\"type\", type]];\n if (type !== \"min\" && type !== \"max\") {\n // what if the formula is not correct\n // references cannot be relative :/\n let val = threshold.value;\n if (type === \"formula\") {\n try {\n // Relative references are not supported in formula\n val = adaptFormulaToExcel(threshold.value);\n }\n catch (error) {\n val = threshold.value;\n }\n }\n attrs.push([\"val\", val]); // value is undefined only for type=\"value\")\n }\n return attrs;\n }\n /**\n * This function adapts our Threshold types to their Excel equivalents.\n *\n * if type === \"value\" ,then we must replace it by min or max according to the position\n * if type === \"number\", then it becomes num\n * if type === \"percentage\", it becomes \"percent\"\n * rest of the time, the type is unchanged\n */\n function getExcelThresholdType(type, position) {\n switch (type) {\n case \"value\":\n return position === \"minimum\" ? \"min\" : \"max\";\n case \"number\":\n return \"num\";\n case \"percentage\":\n return \"percent\";\n default:\n return type;\n }\n }\n\n function createDrawing(chartRelIds, sheet, figures) {\n const namespaces = [\n [\"xmlns:xdr\", NAMESPACE.drawing],\n [\"xmlns:r\", RELATIONSHIP_NSR],\n [\"xmlns:a\", DRAWING_NS_A],\n [\"xmlns:c\", DRAWING_NS_C],\n ];\n const figuresNodes = [];\n for (const [figureIndex, figure] of Object.entries(figures)) {\n // position\n const { from, to } = convertFigureData(figure, sheet);\n const chartId = convertChartId(figure.id);\n const cNvPrAttrs = [\n [\"id\", chartId],\n [\"name\", `Chart ${chartId}`],\n [\"title\", \"Chart\"],\n ];\n figuresNodes.push(escapeXml /*xml*/ `\n \n \n ${from.col}\n ${from.colOff}\n ${from.row}\n ${from.rowOff}\n \n \n ${to.col}\n ${to.colOff}\n ${to.row}\n ${to.rowOff}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n `);\n }\n const xml = escapeXml /*xml*/ `\n \n ${joinXmlNodes(figuresNodes)}\n \n `;\n return parseXML(xml);\n }\n /**\n * Returns the coordinates of topLeft (from) and BottomRight (to) of the chart in English Metric Units (EMU)\n */\n function convertFigureData(figure, sheet) {\n const { x, y, height, width } = figure;\n const cols = Object.values(sheet.cols);\n const rows = Object.values(sheet.rows);\n const { index: colFrom, offset: offsetColFrom } = figureCoordinates(cols, x);\n const { index: colTo, offset: offsetColTo } = figureCoordinates(cols, x + width);\n const { index: rowFrom, offset: offsetRowFrom } = figureCoordinates(rows, y);\n const { index: rowTo, offset: offsetRowTo } = figureCoordinates(rows, y + height);\n return {\n from: {\n col: colFrom,\n colOff: offsetColFrom,\n row: rowFrom,\n rowOff: offsetRowFrom,\n },\n to: {\n col: colTo,\n colOff: offsetColTo,\n row: rowTo,\n rowOff: offsetRowTo,\n },\n };\n }\n /** Returns figure coordinates in EMU for a specific header dimension\n * See https://docs.microsoft.com/en-us/windows/win32/vml/msdn-online-vml-units#other-units-of-measurement\n */\n function figureCoordinates(headers, position) {\n let currentPosition = 0;\n for (const [headerIndex, header] of Object.entries(headers)) {\n if (currentPosition <= position && position < currentPosition + header.size) {\n return {\n index: parseInt(headerIndex),\n offset: convertDotValueToEMU(position - currentPosition + FIGURE_BORDER_SIZE),\n };\n }\n else {\n currentPosition += header.size;\n }\n }\n return {\n index: headers.length - 1,\n offset: convertDotValueToEMU(position - currentPosition + FIGURE_BORDER_SIZE),\n };\n }\n\n function addNumberFormats(numFmts) {\n const numFmtNodes = [];\n for (let [index, numFmt] of Object.entries(numFmts)) {\n const numFmtAttrs = [\n [\"numFmtId\", parseInt(index) + FIRST_NUMFMT_ID],\n [\"formatCode\", numFmt.format],\n ];\n numFmtNodes.push(escapeXml /*xml*/ `\n \n `);\n }\n return escapeXml /*xml*/ `\n \n ${joinXmlNodes(numFmtNodes)}\n \n `;\n }\n function addFont(font) {\n if (isObjectEmptyRecursive(font)) {\n return escapeXml /*xml*/ ``;\n }\n return escapeXml /*xml*/ `\n \n ${font.bold ? escapeXml /*xml*/ `` : \"\"}\n ${font.italic ? escapeXml /*xml*/ `` : \"\"}\n ${font.underline ? escapeXml /*xml*/ `` : \"\"}\n ${font.strike ? escapeXml /*xml*/ `` : \"\"}\n ${font.size ? escapeXml /*xml*/ `` : \"\"}\n ${font.color && font.color.rgb\n ? escapeXml /*xml*/ ``\n : \"\"}\n ${font.name ? escapeXml /*xml*/ `` : \"\"}\n \n `;\n }\n function addFonts(fonts) {\n return escapeXml /*xml*/ `\n \n ${joinXmlNodes(Object.values(fonts).map(addFont))}\n \n `;\n }\n function addFills(fills) {\n const fillNodes = [];\n for (let fill of Object.values(fills)) {\n if (fill.reservedAttribute !== undefined) {\n fillNodes.push(escapeXml /*xml*/ `\n \n \n \n `);\n }\n else {\n fillNodes.push(escapeXml /*xml*/ `\n \n \n \n \n \n \n `);\n }\n }\n return escapeXml /*xml*/ `\n \n ${joinXmlNodes(fillNodes)}\n \n `;\n }\n function addBorders(borders) {\n const borderNodes = [];\n for (let border of Object.values(borders)) {\n borderNodes.push(escapeXml /*xml*/ `\n \n \n \n \n \n \n \n `);\n }\n return escapeXml /*xml*/ `\n \n ${joinXmlNodes(borderNodes)}\n \n `;\n }\n function formatBorderAttribute(description) {\n if (!description) {\n return escapeXml ``;\n }\n return formatAttributes([\n [\"style\", description.style],\n [\"color\", toXlsxHexColor(description.color.rgb)],\n ]);\n }\n function addStyles(styles) {\n const styleNodes = [];\n for (let style of styles) {\n const attributes = [\n [\"numFmtId\", style.numFmtId],\n [\"fillId\", style.fillId],\n [\"fontId\", style.fontId],\n [\"borderId\", style.borderId],\n ];\n // Note: the apply${substyleName} does not seem to be required\n const alignAttrs = [];\n if (style.alignment && style.alignment.vertical) {\n alignAttrs.push([\"vertical\", style.alignment.vertical]);\n }\n if (style.alignment && style.alignment.horizontal) {\n alignAttrs.push([\"horizontal\", style.alignment.horizontal]);\n }\n styleNodes.push(escapeXml /*xml*/ `\n \n ${alignAttrs ? escapeXml /*xml*/ `` : \"\"}\n \n `);\n }\n return escapeXml /*xml*/ `\n \n ${joinXmlNodes(styleNodes)}\n \n `;\n }\n /**\n * DXFS : Differential Formatting Records - Conditional formats\n */\n function addCellWiseConditionalFormatting(dxfs // cell-wise CF\n ) {\n const dxfNodes = [];\n for (const dxf of dxfs) {\n let fontNode = escapeXml ``;\n if (dxf.font) {\n fontNode = addFont(dxf.font);\n }\n let fillNode = escapeXml ``;\n if (dxf.fill) {\n fillNode = escapeXml /*xml*/ `\n \n \n \n \n \n `;\n }\n dxfNodes.push(escapeXml /*xml*/ `\n \n ${fontNode}\n ${fillNode}\n \n `);\n }\n return escapeXml /*xml*/ `\n \n ${joinXmlNodes(dxfNodes)}\n \n `;\n }\n\n const TABLE_DEFAULT_STYLE = escapeXml /*xml*/ ``;\n function createTable(table, tableId, sheetData) {\n const tableAttributes = [\n [\"id\", tableId],\n [\"name\", `Table${tableId}`],\n [\"displayName\", `Table${tableId}`],\n [\"ref\", table.range],\n [\"xmlns\", NAMESPACE.table],\n [\"xmlns:xr\", NAMESPACE.revision],\n [\"xmlns:xr3\", NAMESPACE.revision3],\n [\"xmlns:mc\", NAMESPACE.markupCompatibility],\n ];\n const xml = escapeXml /*xml*/ `\n \n ${addAutoFilter(table)}\n ${addTableColumns(table, sheetData)}\n ${TABLE_DEFAULT_STYLE}\n
\n `;\n return parseXML(xml);\n }\n function addAutoFilter(table) {\n const autoFilterAttributes = [[\"ref\", table.range]];\n return escapeXml /*xml*/ `\n \n ${joinXmlNodes(addFilterColumns(table))}\n \n `;\n }\n function addFilterColumns(table) {\n const tableZone = toZone(table.range);\n const columns = [];\n for (const i of range(0, zoneToDimension(tableZone).width)) {\n const filter = table.filters[i];\n if (!filter || !filter.filteredValues.length) {\n continue;\n }\n const colXml = escapeXml /*xml*/ `\n \n ${addFilter(filter)}\n \n `;\n columns.push(colXml);\n }\n return columns;\n }\n function addFilter(filter) {\n const filterValues = filter.filteredValues.map((val) => escapeXml /*xml*/ ``);\n return escapeXml /*xml*/ `\n \n ${joinXmlNodes(filterValues)}\n \n`;\n }\n function addTableColumns(table, sheetData) {\n var _a;\n const tableZone = toZone(table.range);\n const columns = [];\n for (const i of range(0, zoneToDimension(tableZone).width)) {\n const colHeaderXc = toXC(tableZone.left + i, tableZone.top);\n const colName = ((_a = sheetData.cells[colHeaderXc]) === null || _a === void 0 ? void 0 : _a.content) || `col${i}`;\n const colAttributes = [\n [\"id\", i + 1],\n [\"name\", colName],\n ];\n columns.push(escapeXml /*xml*/ ``);\n }\n return escapeXml /*xml*/ `\n \n ${joinXmlNodes(columns)}\n \n `;\n }\n\n function addColumns(cols) {\n if (!Object.values(cols).length) {\n return escapeXml ``;\n }\n const colNodes = [];\n for (let [id, col] of Object.entries(cols)) {\n // Always force our own col width\n const attributes = [\n [\"min\", parseInt(id) + 1],\n [\"max\", parseInt(id) + 1],\n [\"width\", convertWidthToExcel(col.size || DEFAULT_CELL_WIDTH)],\n [\"customWidth\", 1],\n [\"hidden\", col.isHidden ? 1 : 0],\n ];\n colNodes.push(escapeXml /*xml*/ `\n \n `);\n }\n return escapeXml /*xml*/ `\n \n ${joinXmlNodes(colNodes)}\n \n `;\n }\n function addRows(construct, data, sheet) {\n const rowNodes = [];\n for (let r = 0; r < sheet.rowNumber; r++) {\n const rowAttrs = [[\"r\", r + 1]];\n const row = sheet.rows[r] || {};\n // Always force our own row height\n rowAttrs.push([\"ht\", convertHeightToExcel(row.size || DEFAULT_CELL_HEIGHT)], [\"customHeight\", 1], [\"hidden\", row.isHidden ? 1 : 0]);\n const cellNodes = [];\n for (let c = 0; c < sheet.colNumber; c++) {\n const xc = toXC(c, r);\n const cell = sheet.cells[xc];\n if (cell) {\n const attributes = [[\"r\", xc]];\n // style\n const id = normalizeStyle(construct, extractStyle(cell, data));\n attributes.push([\"s\", id]);\n let additionalAttrs = [];\n let cellNode = escapeXml ``;\n // Either formula or static value inside the cell\n if (cell.isFormula) {\n ({ attrs: additionalAttrs, node: cellNode } = addFormula(cell));\n }\n else if (cell.content && isMarkdownLink(cell.content)) {\n const { label } = parseMarkdownLink(cell.content);\n ({ attrs: additionalAttrs, node: cellNode } = addContent(label, construct.sharedStrings));\n }\n else if (cell.content && cell.content !== \"\") {\n const isTableHeader = isCellTableHeader(c, r, sheet);\n ({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader));\n }\n attributes.push(...additionalAttrs);\n cellNodes.push(escapeXml /*xml*/ `\n \n ${cellNode}\n \n `);\n }\n }\n if (cellNodes.length || row.size !== DEFAULT_CELL_HEIGHT || row.isHidden) {\n rowNodes.push(escapeXml /*xml*/ `\n \n ${joinXmlNodes(cellNodes)}\n \n `);\n }\n }\n return escapeXml /*xml*/ `\n \n ${joinXmlNodes(rowNodes)}\n \n `;\n }\n function isCellTableHeader(col, row, sheet) {\n return sheet.filterTables.some((table) => {\n const zone = toZone(table.range);\n const headerZone = { ...zone, bottom: zone.top };\n return isInside(col, row, headerZone);\n });\n }\n function addHyperlinks(construct, data, sheetIndex) {\n var _a;\n const sheet = data.sheets[sheetIndex];\n const cells = sheet.cells;\n const linkNodes = [];\n for (const xc in cells) {\n const content = (_a = cells[xc]) === null || _a === void 0 ? void 0 : _a.content;\n if (content && isMarkdownLink(content)) {\n const { label, url } = parseMarkdownLink(content);\n if (isSheetUrl(url)) {\n const sheetId = parseSheetUrl(url);\n const sheet = data.sheets.find((sheet) => sheet.id === sheetId);\n const location = sheet ? `${sheet.name}!A1` : INCORRECT_RANGE_STRING;\n linkNodes.push(escapeXml /*xml*/ `\n \n `);\n }\n else {\n const linkRelId = addRelsToFile(construct.relsFiles, `xl/worksheets/_rels/sheet${sheetIndex}.xml.rels`, {\n target: withHttps(url),\n type: XLSX_RELATION_TYPE.hyperlink,\n targetMode: \"External\",\n });\n linkNodes.push(escapeXml /*xml*/ `\n \n `);\n }\n }\n }\n if (!linkNodes.length) {\n return escapeXml ``;\n }\n return escapeXml /*xml*/ `\n \n ${joinXmlNodes(linkNodes)}\n \n `;\n }\n function addMerges(merges) {\n if (merges.length) {\n const mergeNodes = merges.map((merge) => escapeXml /*xml*/ ``);\n return escapeXml /*xml*/ `\n \n ${joinXmlNodes(mergeNodes)}\n \n `;\n }\n else\n return escapeXml ``;\n }\n function addSheetViews(sheet) {\n const panes = sheet.panes;\n let splitPanes = escapeXml /*xml*/ ``;\n if (panes && (panes.xSplit || panes.ySplit)) {\n const xc = toXC(panes.xSplit, panes.ySplit);\n //workbookViewId should be defined in the workbook file but it seems like Excel has a default behaviour.\n const xSplit = panes.xSplit ? escapeXml `xSplit=\"${panes.xSplit}\"` : \"\";\n const ySplit = panes.ySplit ? escapeXml `ySplit=\"${panes.ySplit}\"` : \"\";\n const topRight = panes.xSplit ? escapeXml `` : \"\";\n const bottomLeft = panes.ySplit ? escapeXml `` : \"\";\n const bottomRight = panes.xSplit && panes.ySplit ? escapeXml `` : \"\";\n splitPanes = escapeXml /*xml*/ `\n \n ${topRight}\n ${bottomLeft}\n ${bottomRight}\n `;\n }\n let sheetView = escapeXml /*xml*/ `\n \n \n ${splitPanes}\n \n \n `;\n return sheetView;\n }\n\n /**\n * Return the spreadsheet data in the Office Open XML file format.\n * See ECMA-376 standard.\n * https://www.ecma-international.org/publications-and-standards/standards/ecma-376/\n */\n function getXLSX(data) {\n const files = [];\n const construct = getDefaultXLSXStructure();\n files.push(createWorkbook(data, construct));\n files.push(...createWorksheets(data, construct));\n files.push(createStylesSheet(construct));\n files.push(createSharedStrings(construct.sharedStrings));\n files.push(...createRelsFiles(construct.relsFiles));\n files.push(createContentTypes(files));\n files.push(createRelRoot());\n return {\n name: `my_spreadsheet.xlsx`,\n files,\n };\n }\n function createWorkbook(data, construct) {\n const namespaces = [\n [\"xmlns\", NAMESPACE[\"workbook\"]],\n [\"xmlns:r\", RELATIONSHIP_NSR],\n ];\n const sheetNodes = [];\n for (const [index, sheet] of Object.entries(data.sheets)) {\n const attributes = [\n [\"state\", sheet.isVisible ? \"visible\" : \"hidden\"],\n [\"name\", sheet.name],\n [\"sheetId\", parseInt(index) + 1],\n [\"r:id\", `rId${parseInt(index) + 1}`],\n ];\n sheetNodes.push(escapeXml /*xml*/ `\n \n `);\n addRelsToFile(construct.relsFiles, \"xl/_rels/workbook.xml.rels\", {\n type: XLSX_RELATION_TYPE.sheet,\n target: `worksheets/sheet${index}.xml`,\n });\n }\n const xml = escapeXml /*xml*/ `\n \n \n ${joinXmlNodes(sheetNodes)}\n \n \n `;\n return createXMLFile(parseXML(xml), \"xl/workbook.xml\", \"workbook\");\n }\n function createWorksheets(data, construct) {\n const files = [];\n let currentTableIndex = 1;\n for (const [sheetIndex, sheet] of Object.entries(data.sheets)) {\n const namespaces = [\n [\"xmlns\", NAMESPACE[\"worksheet\"]],\n [\"xmlns:r\", RELATIONSHIP_NSR],\n ];\n const sheetFormatAttributes = [\n [\"defaultRowHeight\", convertHeightToExcel(DEFAULT_CELL_HEIGHT)],\n [\"defaultColWidth\", convertWidthToExcel(DEFAULT_CELL_WIDTH)],\n ];\n const tablesNode = createTablesForSheet(sheet, sheetIndex, currentTableIndex, construct, files);\n currentTableIndex += sheet.filterTables.length;\n // Figures and Charts\n let drawingNode = escapeXml ``;\n const charts = sheet.charts;\n if (charts.length) {\n const chartRelIds = [];\n for (const chart of charts) {\n const xlsxChartId = convertChartId(chart.id);\n const chartRelId = addRelsToFile(construct.relsFiles, `xl/drawings/_rels/drawing${sheetIndex}.xml.rels`, {\n target: `../charts/chart${xlsxChartId}.xml`,\n type: XLSX_RELATION_TYPE.chart,\n });\n chartRelIds.push(chartRelId);\n files.push(createXMLFile(createChart(chart, sheetIndex, data), `xl/charts/chart${xlsxChartId}.xml`, \"chart\"));\n }\n const drawingRelId = addRelsToFile(construct.relsFiles, `xl/worksheets/_rels/sheet${sheetIndex}.xml.rels`, {\n target: `../drawings/drawing${sheetIndex}.xml`,\n type: XLSX_RELATION_TYPE.drawing,\n });\n files.push(createXMLFile(createDrawing(chartRelIds, sheet, charts), `xl/drawings/drawing${sheetIndex}.xml`, \"drawing\"));\n drawingNode = escapeXml /*xml*/ ``;\n }\n const sheetXml = escapeXml /*xml*/ `\n \n ${addSheetViews(sheet)}\n \n ${addColumns(sheet.cols)}\n ${addRows(construct, data, sheet)}\n ${addMerges(sheet.merges)}\n ${joinXmlNodes(addConditionalFormatting(construct.dxfs, sheet.conditionalFormats))}\n ${addHyperlinks(construct, data, sheetIndex)}\n ${drawingNode}\n ${tablesNode}\n \n `;\n files.push(createXMLFile(parseXML(sheetXml), `xl/worksheets/sheet${sheetIndex}.xml`, \"sheet\"));\n }\n addRelsToFile(construct.relsFiles, \"xl/_rels/workbook.xml.rels\", {\n type: XLSX_RELATION_TYPE.sharedStrings,\n target: \"sharedStrings.xml\",\n });\n addRelsToFile(construct.relsFiles, \"xl/_rels/workbook.xml.rels\", {\n type: XLSX_RELATION_TYPE.styles,\n target: \"styles.xml\",\n });\n return files;\n }\n /**\n * Create xlsx files for each tables contained in the given sheet, and add them to the XLSXStructure ans XLSXExportFiles.\n *\n * Return an XML string that should be added in the sheet to link these table to the sheet.\n */\n function createTablesForSheet(sheetData, sheetId, startingTableId, construct, files) {\n let currentTableId = startingTableId;\n if (!sheetData.filterTables.length)\n return new XMLString(\"\");\n const sheetRelFile = `xl/worksheets/_rels/sheet${sheetId}.xml.rels`;\n const tableParts = [];\n for (const table of sheetData.filterTables) {\n const tableRelId = addRelsToFile(construct.relsFiles, sheetRelFile, {\n target: `../tables/table${currentTableId}.xml`,\n type: XLSX_RELATION_TYPE.table,\n });\n files.push(createXMLFile(createTable(table, currentTableId, sheetData), `xl/tables/table${currentTableId}.xml`, \"table\"));\n tableParts.push(escapeXml /*xml*/ ``);\n currentTableId++;\n }\n return escapeXml /*xml*/ `\n \n ${joinXmlNodes(tableParts)}\n \n`;\n }\n function createStylesSheet(construct) {\n const namespaces = [\n [\"xmlns\", NAMESPACE[\"styleSheet\"]],\n [\"xmlns:r\", RELATIONSHIP_NSR],\n ];\n const styleXml = escapeXml /*xml*/ `\n \n ${addNumberFormats(construct.numFmts)}\n ${addFonts(construct.fonts)}\n ${addFills(construct.fills)}\n ${addBorders(construct.borders)}\n ${addStyles(construct.styles)}\n ${addCellWiseConditionalFormatting(construct.dxfs)}\n \n `;\n return createXMLFile(parseXML(styleXml), \"xl/styles.xml\", \"styles\");\n }\n function createSharedStrings(strings) {\n const namespaces = [\n [\"xmlns\", NAMESPACE[\"sst\"]],\n [\"count\", strings.length],\n [\"uniqueCount\", strings.length],\n ];\n const stringNodes = strings.map((string) => escapeXml /*xml*/ `${string}`);\n const xml = escapeXml /*xml*/ `\n \n ${joinXmlNodes(stringNodes)}\n \n `;\n return createXMLFile(parseXML(xml), \"xl/sharedStrings.xml\", \"sharedStrings\");\n }\n function createRelsFiles(relsFiles) {\n const XMLRelsFiles = [];\n for (const relFile of relsFiles) {\n const relationNodes = [];\n for (const rel of relFile.rels) {\n const attributes = [\n [\"Id\", rel.id],\n [\"Target\", rel.target],\n [\"Type\", rel.type],\n ];\n if (rel.targetMode) {\n attributes.push([\"TargetMode\", rel.targetMode]);\n }\n relationNodes.push(escapeXml /*xml*/ `\n \n `);\n }\n const xml = escapeXml /*xml*/ `\n \n ${joinXmlNodes(relationNodes)}\n \n `;\n XMLRelsFiles.push(createXMLFile(parseXML(xml), relFile.path));\n }\n return XMLRelsFiles;\n }\n function createContentTypes(files) {\n const overrideNodes = [];\n for (const file of files) {\n if (file.contentType) {\n overrideNodes.push(createOverride(\"/\" + file.path, CONTENT_TYPES[file.contentType]));\n }\n }\n const xml = escapeXml /*xml*/ `\n \n \n \n ${joinXmlNodes(overrideNodes)}\n \n `;\n return createXMLFile(parseXML(xml), \"[Content_Types].xml\");\n }\n function createRelRoot() {\n const attributes = [\n [\"Id\", \"rId1\"],\n [\"Type\", XLSX_RELATION_TYPE.document],\n [\"Target\", \"xl/workbook.xml\"],\n ];\n const xml = escapeXml /*xml*/ `\n \n \n \n `;\n return createXMLFile(parseXML(xml), \"_rels/.rels\");\n }\n\n var Status;\n (function (Status) {\n Status[Status[\"Ready\"] = 0] = \"Ready\";\n Status[Status[\"Running\"] = 1] = \"Running\";\n Status[Status[\"RunningCore\"] = 2] = \"RunningCore\";\n Status[Status[\"Finalizing\"] = 3] = \"Finalizing\";\n })(Status || (Status = {}));\n class Model extends EventBus {\n constructor(data = {}, config = {}, stateUpdateMessages = [], uuidGenerator = new UuidGenerator(), verboseImport = true) {\n super();\n this.corePlugins = [];\n this.featurePlugins = [];\n this.statefulUIPlugins = [];\n this.coreViewsPlugins = [];\n /**\n * In a collaborative context, some commands can be replayed, we have to ensure\n * that these commands are not replayed on the UI plugins.\n */\n this.isReplayingCommand = false;\n /**\n * A plugin can draw some contents on the canvas. But even better: it can do\n * so multiple times. The order of the render calls will determine a list of\n * \"layers\" (i.e., earlier calls will be obviously drawn below later calls).\n * This list simply keeps the renderers+layer information so the drawing code\n * can just iterate on it\n */\n this.renderers = [];\n /**\n * Internal status of the model. Important for command handling coordination\n */\n this.status = 0 /* Status.Ready */;\n /**\n * The dispatch method is the only entry point to manipulate data in the model.\n * This is through this method that commands are dispatched most of the time\n * recursively until no plugin want to react anymore.\n *\n * CoreCommands dispatched from this function are saved in the history.\n *\n * Small technical detail: it is defined as an arrow function. There are two\n * reasons for this:\n * 1. this means that the dispatch method can be \"detached\" from the model,\n * which is done when it is put in the environment (see the Spreadsheet\n * component)\n * 2. This allows us to define its type by using the interface CommandDispatcher\n */\n this.dispatch = (type, payload) => {\n const command = { ...payload, type };\n let status = this.status;\n if (this.getters.isReadonly() && !canExecuteInReadonly(command)) {\n return new DispatchResult(67 /* CommandResult.Readonly */);\n }\n if (!this.session.canApplyOptimisticUpdate()) {\n return new DispatchResult(64 /* CommandResult.WaitingSessionConfirmation */);\n }\n switch (status) {\n case 0 /* Status.Ready */:\n const result = this.checkDispatchAllowed(command);\n if (!result.isSuccessful) {\n return result;\n }\n this.status = 1 /* Status.Running */;\n const { changes, commands } = this.state.recordChanges(() => {\n if (isCoreCommand(command)) {\n this.state.addCommand(command);\n }\n this.dispatchToHandlers(this.handlers, command);\n this.finalize();\n });\n this.session.save(commands, changes);\n this.status = 0 /* Status.Ready */;\n this.trigger(\"update\");\n break;\n case 1 /* Status.Running */:\n if (isCoreCommand(command)) {\n const dispatchResult = this.checkDispatchAllowed(command);\n if (!dispatchResult.isSuccessful) {\n return dispatchResult;\n }\n this.state.addCommand(command);\n }\n this.dispatchToHandlers(this.handlers, command);\n break;\n case 3 /* Status.Finalizing */:\n throw new Error(\"Cannot dispatch commands in the finalize state\");\n case 2 /* Status.RunningCore */:\n if (isCoreCommand(command)) {\n throw new Error(`A UI plugin cannot dispatch ${type} while handling a core command`);\n }\n this.dispatchToHandlers(this.handlers, command);\n }\n return DispatchResult.Success;\n };\n /**\n * Dispatch a command from a Core Plugin (or the History).\n * A command dispatched from this function is not added to the history.\n */\n this.dispatchFromCorePlugin = (type, payload) => {\n const command = { ...payload, type };\n const previousStatus = this.status;\n this.status = 2 /* Status.RunningCore */;\n const handlers = this.isReplayingCommand\n ? [this.range, ...this.corePlugins, ...this.coreViewsPlugins]\n : this.handlers;\n this.dispatchToHandlers(handlers, command);\n this.status = previousStatus;\n return DispatchResult.Success;\n };\n stateUpdateMessages = repairInitialMessages(data, stateUpdateMessages);\n const workbookData = load(data, verboseImport);\n this.state = new StateObserver();\n this.uuidGenerator = uuidGenerator;\n this.config = this.setupConfig(config);\n this.session = this.setupSession(workbookData.revisionId);\n this.history = new LocalHistory(this.dispatchFromCorePlugin, this.session);\n this.coreGetters = {};\n this.range = new RangeAdapter(this.coreGetters);\n this.coreGetters.getRangeString = this.range.getRangeString.bind(this.range);\n this.coreGetters.getRangeFromSheetXC = this.range.getRangeFromSheetXC.bind(this.range);\n this.coreGetters.createAdaptedRanges = this.range.createAdaptedRanges.bind(this.range);\n this.coreGetters.getRangeDataFromXc = this.range.getRangeDataFromXc.bind(this.range);\n this.coreGetters.getRangeDataFromZone = this.range.getRangeDataFromZone.bind(this.range);\n this.coreGetters.getRangeFromRangeData = this.range.getRangeFromRangeData.bind(this.range);\n this.coreGetters.getSelectionRangeString = this.range.getSelectionRangeString.bind(this.range);\n this.getters = {\n isReadonly: () => this.config.mode === \"readonly\" || this.config.mode === \"dashboard\",\n isDashboard: () => this.config.mode === \"dashboard\",\n canUndo: this.history.canUndo.bind(this.history),\n canRedo: this.history.canRedo.bind(this.history),\n getClient: this.session.getClient.bind(this.session),\n getConnectedClients: this.session.getConnectedClients.bind(this.session),\n isFullySynchronized: this.session.isFullySynchronized.bind(this.session),\n };\n this.uuidGenerator.setIsFastStrategy(true);\n // Initiate stream processor\n this.selection = new SelectionStreamProcessor(this.getters);\n this.corePluginConfig = this.setupCorePluginConfig();\n this.uiPluginConfig = this.setupUiPluginConfig();\n // registering plugins\n for (let Plugin of corePluginRegistry.getAll()) {\n this.setupCorePlugin(Plugin, workbookData);\n }\n Object.assign(this.getters, this.coreGetters);\n for (let Plugin of statefulUIPluginRegistry.getAll()) {\n this.statefulUIPlugins.push(this.setupUiPlugin(Plugin));\n }\n for (let Plugin of coreViewsPluginRegistry.getAll()) {\n this.coreViewsPlugins.push(this.setupUiPlugin(Plugin));\n }\n for (let Plugin of featurePluginRegistry.getAll()) {\n this.featurePlugins.push(this.setupUiPlugin(Plugin));\n }\n this.uuidGenerator.setIsFastStrategy(false);\n // starting plugins\n this.dispatch(\"START\");\n // Model should be the last permanent subscriber in the list since he should render\n // after all changes have been applied to the other subscribers (plugins)\n this.selection.observe(this, {\n handleEvent: () => this.trigger(\"update\"),\n });\n // This should be done after construction of LocalHistory due to order of\n // events\n this.setupSessionEvents();\n // Load the initial revisions\n this.session.loadInitialMessages(stateUpdateMessages);\n this.joinSession();\n if (config.snapshotRequested) {\n this.session.snapshot(this.exportData());\n this.garbageCollectExternalResources();\n }\n // mark all models as \"raw\", so they will not be turned into reactive objects\n // by owl, since we do not rely on reactivity\n owl.markRaw(this);\n }\n get handlers() {\n return [this.range, ...this.corePlugins, ...this.allUIPlugins, this.history];\n }\n get allUIPlugins() {\n return [...this.statefulUIPlugins, ...this.coreViewsPlugins, ...this.featurePlugins];\n }\n joinSession() {\n this.session.join(this.config.client);\n }\n leaveSession() {\n this.session.leave();\n }\n setupUiPlugin(Plugin) {\n const plugin = new Plugin(this.uiPluginConfig);\n for (let name of Plugin.getters) {\n if (!(name in plugin)) {\n throw new Error(`Invalid getter name: ${name} for plugin ${plugin.constructor}`);\n }\n if (name in this.getters) {\n throw new Error(`Getter \"${name}\" is already defined.`);\n }\n this.getters[name] = plugin[name].bind(plugin);\n }\n const layers = Plugin.layers.map((l) => [plugin, l]);\n this.renderers.push(...layers);\n this.renderers.sort((p1, p2) => p1[1] - p2[1]);\n return plugin;\n }\n /**\n * Initialize and properly configure a plugin.\n *\n * This method is private for now, but if the need arise, there is no deep\n * reason why the model could not add dynamically a plugin while it is running.\n */\n setupCorePlugin(Plugin, data) {\n const plugin = new Plugin(this.corePluginConfig);\n for (let name of Plugin.getters) {\n if (!(name in plugin)) {\n throw new Error(`Invalid getter name: ${name} for plugin ${plugin.constructor}`);\n }\n if (name in this.coreGetters) {\n throw new Error(`Getter \"${name}\" is already defined.`);\n }\n this.coreGetters[name] = plugin[name].bind(plugin);\n }\n plugin.import(data);\n this.corePlugins.push(plugin);\n }\n onRemoteRevisionReceived({ commands }) {\n for (let command of commands) {\n const previousStatus = this.status;\n this.status = 2 /* Status.RunningCore */;\n this.dispatchToHandlers(this.statefulUIPlugins, command);\n this.status = previousStatus;\n }\n this.finalize();\n }\n setupSession(revisionId) {\n const session = new Session(buildRevisionLog(revisionId, this.state.recordChanges.bind(this.state), (command) => {\n const result = this.checkDispatchAllowed(command);\n if (!result.isSuccessful) {\n return;\n }\n this.isReplayingCommand = true;\n this.dispatchToHandlers([this.range, ...this.corePlugins, ...this.coreViewsPlugins], command);\n this.isReplayingCommand = false;\n }), this.config.transportService, revisionId);\n return session;\n }\n setupSessionEvents() {\n this.session.on(\"remote-revision-received\", this, this.onRemoteRevisionReceived);\n this.session.on(\"revision-redone\", this, this.finalize);\n this.session.on(\"revision-undone\", this, this.finalize);\n // How could we improve communication between the session and UI?\n // It feels weird to have the model piping specific session events to its own bus.\n this.session.on(\"unexpected-revision-id\", this, () => this.trigger(\"unexpected-revision-id\"));\n this.session.on(\"collaborative-event-received\", this, () => {\n this.trigger(\"update\");\n });\n }\n setupConfig(config) {\n const client = config.client || {\n id: this.uuidGenerator.uuidv4(),\n name: _lt(\"Anonymous\").toString(),\n };\n const transportService = config.transportService || new LocalTransportService();\n return {\n ...config,\n mode: config.mode || \"normal\",\n custom: config.custom || {},\n external: config.external || {},\n transportService,\n client,\n moveClient: () => { },\n snapshotRequested: false,\n notifyUI: (payload) => this.trigger(\"notify-ui\", payload),\n lazyEvaluation: \"lazyEvaluation\" in config ? config.lazyEvaluation : true,\n };\n }\n setupCorePluginConfig() {\n return {\n getters: this.coreGetters,\n stateObserver: this.state,\n range: this.range,\n dispatch: this.dispatchFromCorePlugin,\n uuidGenerator: this.uuidGenerator,\n custom: this.config.custom,\n external: this.config.external,\n };\n }\n setupUiPluginConfig() {\n return {\n getters: this.getters,\n stateObserver: this.state,\n dispatch: this.dispatch,\n selection: this.selection,\n moveClient: this.session.move.bind(this.session),\n custom: this.config.custom,\n uiActions: this.config,\n lazyEvaluation: this.config.lazyEvaluation,\n };\n }\n // ---------------------------------------------------------------------------\n // Command Handling\n // ---------------------------------------------------------------------------\n /**\n * Check if the given command is allowed by all the plugins and the history.\n */\n checkDispatchAllowed(command) {\n const results = this.handlers.map((handler) => handler.allowDispatch(command));\n return new DispatchResult(results.flat());\n }\n finalize() {\n this.status = 3 /* Status.Finalizing */;\n for (const h of this.handlers) {\n h.finalize();\n }\n this.status = 0 /* Status.Ready */;\n }\n /**\n * Dispatch the given command to the given handlers.\n * It will call `beforeHandle` and `handle`\n */\n dispatchToHandlers(handlers, command) {\n command = deepCopy(command);\n for (const handler of handlers) {\n handler.beforeHandle(command);\n }\n for (const handler of handlers) {\n handler.handle(command);\n }\n }\n // ---------------------------------------------------------------------------\n // Grid Rendering\n // ---------------------------------------------------------------------------\n /**\n * When the Grid component is ready (= mounted), it has a reference to its\n * canvas and need to draw the grid on it. This is then done by calling this\n * method, which will dispatch the call to all registered plugins.\n *\n * Note that nothing prevent multiple grid components from calling this method\n * each, or one grid component calling it multiple times with a different\n * context. This is probably the way we should do if we want to be able to\n * freeze a part of the grid (so, we would need to render different zones)\n */\n drawGrid(context) {\n // we make sure here that the viewport is properly positioned: the offsets\n // correspond exactly to a cell\n for (let [renderer, layer] of this.renderers) {\n context.ctx.save();\n renderer.drawGrid(context, layer);\n context.ctx.restore();\n }\n }\n // ---------------------------------------------------------------------------\n // Data Export\n // ---------------------------------------------------------------------------\n /**\n * As the name of this method strongly implies, it is useful when we need to\n * export date out of the model.\n */\n exportData() {\n let data = createEmptyWorkbookData();\n for (let handler of this.handlers) {\n if (handler instanceof CorePlugin) {\n handler.export(data);\n }\n }\n data.revisionId = this.session.getRevisionId() || DEFAULT_REVISION_ID;\n data = JSON.parse(JSON.stringify(data));\n return data;\n }\n updateMode(mode) {\n if (mode !== \"normal\") {\n this.dispatch(\"STOP_EDITION\", { cancel: true });\n }\n // @ts-ignore For testing purposes only\n this.config.mode = mode;\n this.trigger(\"update\");\n }\n /**\n * Exports the current model data into a list of serialized XML files\n * to be zipped together as an *.xlsx file.\n *\n * We need to trigger a cell revaluation on every sheet and ensure that even\n * async functions are evaluated.\n * This prove to be necessary if the client did not trigger that evaluation in the first place\n * (e.g. open a document with several sheet and click on download before visiting each sheet)\n */\n exportXLSX() {\n this.dispatch(\"EVALUATE_CELLS\");\n let data = createEmptyExcelWorkbookData();\n for (let handler of this.handlers) {\n if (handler instanceof BasePlugin) {\n handler.exportForExcel(data);\n }\n }\n data = JSON.parse(JSON.stringify(data));\n return getXLSX(data);\n }\n garbageCollectExternalResources() {\n for (const plugin of this.corePlugins) {\n plugin.garbageCollectExternalResources();\n }\n }\n }\n\n /**\n * We export here all entities that needs to be accessed publicly by Odoo.\n *\n * Note that the __info__ key is actually completed by the build process (see\n * the rollup.config.js file)\n */\n const __info__ = {};\n const SPREADSHEET_DIMENSIONS = {\n MIN_ROW_HEIGHT,\n MIN_COL_WIDTH,\n HEADER_HEIGHT,\n HEADER_WIDTH,\n TOPBAR_HEIGHT,\n BOTTOMBAR_HEIGHT,\n DEFAULT_CELL_WIDTH,\n DEFAULT_CELL_HEIGHT,\n SCROLLBAR_WIDTH: SCROLLBAR_WIDTH$1,\n };\n const registries = {\n autofillModifiersRegistry,\n autofillRulesRegistry,\n cellMenuRegistry,\n colMenuRegistry,\n linkMenuRegistry,\n functionRegistry,\n featurePluginRegistry,\n statefulUIPluginRegistry,\n coreViewsPluginRegistry,\n corePluginRegistry,\n rowMenuRegistry,\n sidePanelRegistry,\n figureRegistry,\n sheetMenuRegistry,\n chartSidePanelComponentRegistry,\n chartComponentRegistry,\n chartRegistry,\n topbarMenuRegistry,\n topbarComponentRegistry,\n clickableCellRegistry,\n otRegistry,\n inverseCommandRegistry,\n urlRegistry,\n cellPopoverRegistry,\n };\n const helpers = {\n args,\n toBoolean,\n toJsDate,\n toNumber,\n toString,\n toXC,\n toZone,\n toCartesian,\n numberToLetters,\n createFullMenuItem,\n UuidGenerator,\n formatValue,\n computeTextWidth,\n createEmptyWorkbookData,\n createEmptySheet,\n createEmptyExcelSheet,\n getDefaultChartJsRuntime,\n chartFontColor,\n getMenuChildren,\n ChartColors,\n EvaluationError,\n CellErrorLevel,\n getFillingMode,\n rgbaToHex,\n colorToRGBA,\n positionToZone,\n isDefined: isDefined$1,\n };\n const links = {\n isMarkdownLink,\n parseMarkdownLink,\n markdownLink,\n openLink,\n urlRepresentation,\n };\n const components = {\n ChartPanel,\n ChartFigure,\n ChartJsComponent,\n Grid,\n GridOverlay,\n ScorecardChart: ScorecardChart$1,\n LineConfigPanel,\n LineBarPieDesignPanel,\n BarConfigPanel,\n LineBarPieConfigPanel,\n GaugeChartConfigPanel,\n GaugeChartDesignPanel,\n ScorecardChartConfigPanel,\n ScorecardChartDesignPanel,\n };\n function addFunction(functionName, functionDescription) {\n functionRegistry.add(functionName, functionDescription);\n }\n\n exports.AbstractChart = AbstractChart;\n exports.CorePlugin = CorePlugin;\n exports.DATETIME_FORMAT = DATETIME_FORMAT;\n exports.DispatchResult = DispatchResult;\n exports.EvaluationError = EvaluationError;\n exports.Model = Model;\n exports.Registry = Registry;\n exports.Revision = Revision;\n exports.SPREADSHEET_DIMENSIONS = SPREADSHEET_DIMENSIONS;\n exports.Spreadsheet = Spreadsheet;\n exports.UIPlugin = UIPlugin;\n exports.__info__ = __info__;\n exports.addFunction = addFunction;\n exports.astToFormula = astToFormula;\n exports.compile = compile;\n exports.components = components;\n exports.convertAstNodes = convertAstNodes;\n exports.coreTypes = coreTypes;\n exports.findCellInNewZone = findCellInNewZone;\n exports.functionCache = functionCache;\n exports.helpers = helpers;\n exports.invalidateEvaluationCommands = invalidateEvaluationCommands;\n exports.links = links;\n exports.load = load;\n exports.parse = parse;\n exports.readonlyAllowedCommands = readonlyAllowedCommands;\n exports.registries = registries;\n exports.setTranslationMethod = setTranslationMethod;\n exports.tokenize = tokenize;\n\n Object.defineProperty(exports, '__esModule', { value: true });\n\n\n __info__.version = '16.1.6';\n __info__.date = '2023-03-23T11:46:03.748Z';\n __info__.hash = '3bad828';\n\n\n})(this.o_spreadsheet = this.o_spreadsheet || {}, owl);\n//# sourceMappingURL=o_spreadsheet.js.map\n", "/** @odoo-module **/\n\nimport { SpreadsheetAction } from \"@documents_spreadsheet/bundle/actions/spreadsheet_action\";\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { _lt } from \"@web/core/l10n/translation\";\nimport { patch } from \"@web/core/utils/patch\";\nconst { topbarMenuRegistry } = spreadsheet.registries;\nconst { useSubEnv } = owl;\n\ntopbarMenuRegistry.addChild(\"add_document_to_dashboard\", [\"file\"], {\n name: _lt(\"Add to dashboard\"),\n sequence: 200,\n isVisible: (env) => env.canAddDocumentAsDashboard,\n action: (env) => env.createDashboardFromDocument(env.model),\n});\n\n/** @typedef {import(\"@spreadsheet/o_spreadsheet/o_spreadsheet\").Model} Model */\n\npatch(SpreadsheetAction.prototype, \"spreadsheet_dashboard_documents.SpreadsheetAction\", {\n setup() {\n this._super();\n useSubEnv({\n canAddDocumentAsDashboard: true,\n createDashboardFromDocument: this._createDashboardFromDocument.bind(this),\n });\n },\n\n /**\n * @param {Model} model\n * @private\n */\n async _createDashboardFromDocument(model) {\n const resId = this.resId;\n const name = this.state.spreadsheetName;\n await this.env.services.orm.write(\"documents.document\", [resId], {\n raw: JSON.stringify(model.exportData()),\n });\n this.env.services.action.doAction(\n {\n name: this.env._t(\"Name your dashboard and select its section\"),\n type: \"ir.actions.act_window\",\n view_mode: \"form\",\n views: [[false, \"form\"]],\n target: \"new\",\n res_model: \"spreadsheet.document.to.dashboard\",\n },\n {\n additionalContext: {\n default_document_id: resId,\n default_name: name,\n },\n }\n );\n },\n});\n", "/** @odoo-module */\nimport { camelToSnakeObject, toServerDateString } from \"@spreadsheet/helpers/helpers\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { sprintf } from \"@web/core/utils/strings\";\n\nimport { ServerData } from \"@spreadsheet/data_sources/server_data\";\n\n/**\n * @typedef {import(\"./accounting_functions\").DateRange} DateRange\n */\n\nexport class AccountingDataSource {\n constructor(services) {\n this.serverData = new ServerData(services.orm, {\n whenDataIsFetched: () => services.notify(),\n });\n }\n\n /**\n * Gets the total credit for a given account code prefix\n * @param {string[]} codes prefixes of the accounts codes\n * @param {DateRange} dateRange start date of the period to look\n * @param {number} offset end date of the period to look\n * @param {number} companyId specific company to target\n * @param {boolean} includeUnposted wether or not select unposted entries\n * @returns {number | undefined}\n */\n getCredit(codes, dateRange, offset, companyId, includeUnposted) {\n const data = this._fetchAccountData(codes, dateRange, offset, companyId, includeUnposted);\n return data.credit;\n }\n\n /**\n * Gets the total debit for a given account code prefix\n * @param {string[]} codes prefixes of the accounts codes\n * @param {DateRange} dateRange start date of the period to look\n * @param {number} offset end date of the period to look\n * @param {number} companyId specific company to target\n * @param {boolean} includeUnposted wether or not select unposted entries\n * @returns {number | undefined}\n */\n getDebit(codes, dateRange, offset, companyId, includeUnposted) {\n const data = this._fetchAccountData(codes, dateRange, offset, companyId, includeUnposted);\n return data.debit;\n }\n\n /**\n * @param {Date} date\n * @param {number | null} companyId\n * @returns {string}\n */\n getFiscalStartDate(date, companyId) {\n return this._fetchCompanyData(date, companyId).start;\n }\n\n /**\n * @param {Date} date\n * @param {number | null} companyId\n * @returns {string}\n */\n getFiscalEndDate(date, companyId) {\n return this._fetchCompanyData(date, companyId).end;\n }\n\n /**\n * @param {string} accountType\n * @returns {string[]}\n */\n getAccountGroupCodes(accountType) {\n return this.serverData.batch.get(\"account.account\", \"get_account_group\", accountType);\n }\n\n /**\n * Fetch the account information (credit/debit) for a given account code\n * @private\n * @param {string[]} codes prefix of the accounts' codes\n * @param {DateRange} dateRange start date of the period to look\n * @param {number} offset end date of the period to look\n * @param {number | null} companyId specific companyId to target\n * @param {boolean} includeUnposted wether or not select unposted entries\n * @returns {{ debit: number, credit: number }}\n */\n _fetchAccountData(codes, dateRange, offset, companyId, includeUnposted) {\n dateRange.year += offset;\n // Excel dates start at 1899-12-30, we should not support date ranges\n // that do not cover dates prior to it.\n // Unfortunately, this check needs to be done right before the server\n // call as a date to low (year <= 1) can raise an error server side.\n if (dateRange.year < 1900) {\n throw new Error(sprintf(_t(\"%s is not a valid year.\"), dateRange.year));\n }\n return this.serverData.batch.get(\n \"account.account\",\n \"spreadsheet_fetch_debit_credit\",\n camelToSnakeObject({ dateRange, codes, companyId, includeUnposted })\n );\n }\n\n /**\n * Fetch the start and end date of the fiscal year enclosing a given date\n * Defaults on the current user company if not provided\n * @private\n * @param {Date} date\n * @param {number | null} companyId\n * @returns {{start: string, end: string}}\n */\n _fetchCompanyData(date, companyId) {\n const result = this.serverData.batch.get(\"res.company\", \"get_fiscal_dates\", {\n date: toServerDateString(date),\n company_id: companyId,\n });\n if (result === false) {\n throw new Error(_t(\"The company fiscal year could not be found.\"));\n }\n return result;\n }\n}\n", "/** @odoo-module **/\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport { sprintf } from \"@web/core/utils/strings\";\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nconst { functionRegistry } = spreadsheet.registries;\nconst { args, toBoolean, toString, toNumber, toJsDate } = spreadsheet.helpers;\n\nconst QuarterRegexp = /^q([1-4])\\/(\\d{4})$/i;\nconst MonthRegexp = /^0?([1-9]|1[0-2])\\/(\\d{4})$/i;\n\n/**\n * @typedef {Object} YearDateRange\n * @property {\"year\"} rangeType\n * @property {number} year\n */\n\n/**\n * @typedef {Object} QuarterDateRange\n * @property {\"quarter\"} rangeType\n * @property {number} year\n * @property {number} quarter\n */\n\n/**\n * @typedef {Object} MonthDateRange\n * @property {\"month\"} rangeType\n * @property {number} year\n * @property {number} month\n */\n\n/**\n * @typedef {Object} DayDateRange\n * @property {\"day\"} rangeType\n * @property {number} year\n * @property {number} month\n * @property {number} day\n */\n\n/**\n * @typedef {YearDateRange | QuarterDateRange | MonthDateRange | DayDateRange} DateRange\n */\n\n/**\n * @param {string} dateRange\n * @returns {QuarterDateRange | undefined}\n */\nfunction parseAccountingQuarter(dateRange) {\n const found = dateRange.match(QuarterRegexp);\n return found\n ? {\n rangeType: \"quarter\",\n year: toNumber(found[2]),\n quarter: toNumber(found[1]),\n }\n : undefined;\n}\n\n/**\n * @param {string} dateRange\n * @returns {MonthDateRange | undefined}\n */\nfunction parseAccountingMonth(dateRange) {\n const found = dateRange.match(MonthRegexp);\n return found\n ? {\n rangeType: \"month\",\n year: toNumber(found[2]),\n month: toNumber(found[1]),\n }\n : undefined;\n}\n\n/**\n * @param {string} dateRange\n * @returns {YearDateRange | undefined}\n */\nfunction parseAccountingYear(dateRange) {\n const dateNumber = toNumber(dateRange);\n // This allows a bit of flexibility for the user if they were to input a\n // numeric value instead of a year.\n // Users won't need to fetch accounting info for year 3000 before a long time\n // And the numeric value 3000 corresponds to 18th march 1908, so it's not an\n //issue to prevent them from fetching accounting data prior to that date.\n if (dateNumber < 3000) {\n return { rangeType: \"year\", year: dateNumber };\n }\n return undefined;\n}\n\n/**\n * @param {string} dateRange\n * @returns {DayDateRange}\n */\nfunction parseAccountingDay(dateRange) {\n const dateNumber = toNumber(dateRange);\n return {\n rangeType: \"day\",\n year: functionRegistry.get(\"YEAR\").compute(dateNumber),\n month: functionRegistry.get(\"MONTH\").compute(dateNumber),\n day: functionRegistry.get(\"DAY\").compute(dateNumber),\n };\n}\n\n/**\n * @param {string | number} dateRange\n * @returns {DateRange}\n */\nexport function parseAccountingDate(dateRange) {\n try {\n dateRange = toString(dateRange).trim();\n return (\n parseAccountingQuarter(dateRange) ||\n parseAccountingMonth(dateRange) ||\n parseAccountingYear(dateRange) ||\n parseAccountingDay(dateRange)\n );\n } catch {\n throw new Error(\n sprintf(\n _t(\n `'%s' is not a valid period. Supported formats are \"21/12/2022\", \"Q1/2022\", \"12/2022\", and \"2022\".`\n ),\n dateRange\n )\n );\n }\n}\n\nconst ODOO_FIN_ARGS = `\n account_codes (string) ${_t(\"The prefix of the accounts.\")}\n date_range (string, date) ${_t(\n `The date range. Supported formats are \"21/12/2022\", \"Q1/2022\", \"12/2022\", and \"2022\".`\n )}\n offset (number, default=0) ${_t(\"Year offset applied to date_range.\")}\n company_id (number, optional) ${_t(\"The company to target (Advanced).\")}\n include_unposted (boolean, default=TRUE) ${_t(\"Set to TRUE to include unposted entries.\")}\n`;\n\nfunctionRegistry.add(\"ODOO.CREDIT\", {\n description: _t(\"Get the total credit for the specified account(s) and period.\"),\n args: args(ODOO_FIN_ARGS),\n returns: [\"NUMBER\"],\n compute: function (\n accountCodes,\n dateRange,\n offset = 0,\n companyId = null,\n includeUnposted = true\n ) {\n accountCodes = toString(accountCodes).split(\",\").sort();\n offset = toNumber(offset);\n dateRange = parseAccountingDate(dateRange);\n includeUnposted = toBoolean(includeUnposted);\n return this.getters.getAccountPrefixCredit(\n accountCodes,\n dateRange,\n offset,\n companyId,\n includeUnposted\n );\n },\n computeFormat: function (\n accountCodes,\n dateRange,\n offset = 0,\n companyId = null,\n includeUnposted = true\n ) {\n return this.getters.getCompanyCurrencyFormat(companyId && companyId.value) || \"#,##0.00\";\n },\n});\n\nfunctionRegistry.add(\"ODOO.DEBIT\", {\n description: _t(\"Get the total debit for the specified account(s) and period.\"),\n args: args(ODOO_FIN_ARGS),\n returns: [\"NUMBER\"],\n compute: function (\n accountCodes,\n dateRange,\n offset = 0,\n companyId = null,\n includeUnposted = true\n ) {\n accountCodes = toString(accountCodes).split(\",\").sort();\n offset = toNumber(offset);\n dateRange = parseAccountingDate(dateRange);\n includeUnposted = toBoolean(includeUnposted);\n return this.getters.getAccountPrefixDebit(\n accountCodes,\n dateRange,\n offset,\n companyId,\n includeUnposted\n );\n },\n computeFormat: function (\n accountCodes,\n dateRange,\n offset = 0,\n companyId = null,\n includeUnposted = true\n ) {\n return this.getters.getCompanyCurrencyFormat(companyId && companyId.value) || \"#,##0.00\";\n },\n});\n\nfunctionRegistry.add(\"ODOO.BALANCE\", {\n description: _t(\"Get the total balance for the specified account(s) and period.\"),\n args: args(ODOO_FIN_ARGS),\n returns: [\"NUMBER\"],\n compute: function (\n accountCodes,\n dateRange,\n offset = 0,\n companyId = null,\n includeUnposted = true\n ) {\n accountCodes = toString(accountCodes).split(\",\").sort();\n offset = toNumber(offset);\n dateRange = parseAccountingDate(dateRange);\n includeUnposted = toBoolean(includeUnposted);\n return (\n this.getters.getAccountPrefixDebit(\n accountCodes,\n dateRange,\n offset,\n companyId,\n includeUnposted\n ) -\n this.getters.getAccountPrefixCredit(\n accountCodes,\n dateRange,\n offset,\n companyId,\n includeUnposted\n )\n );\n },\n computeFormat: function (\n accountCodes,\n dateRange,\n offset = 0,\n companyId = null,\n includeUnposted = true\n ) {\n return this.getters.getCompanyCurrencyFormat(companyId && companyId.value) || \"#,##0.00\";\n },\n});\n\nfunctionRegistry.add(\"ODOO.FISCALYEAR.START\", {\n description: _t(\"Returns the starting date of the fiscal year encompassing the provided date.\"),\n args: args(`\n date (date) ${_t(\"Reference date.\")}\n company_id (number, optional) ${_t(\"The company.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: () => \"m/d/yyyy\",\n compute: function (date, companyId = null) {\n const startDate = this.getters.getFiscalStartDate(\n toJsDate(date),\n companyId === null ? null : toNumber(companyId)\n );\n return toNumber(startDate);\n },\n});\n\nfunctionRegistry.add(\"ODOO.FISCALYEAR.END\", {\n description: _t(\"Returns the ending date of the fiscal year encompassing the provided date.\"),\n args: args(`\n date (date) ${_t(\"Reference date.\")}\n company_id (number, optional) ${_t(\"The company.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: () => \"m/d/yyyy\",\n compute: function (date, companyId = null) {\n const endDate = this.getters.getFiscalEndDate(\n toJsDate(date),\n companyId === null ? null : toNumber(companyId)\n );\n return toNumber(endDate);\n },\n});\n\nfunctionRegistry.add(\"ODOO.ACCOUNT.GROUP\", {\n description: _t(\"Returns the account ids of a given group.\"),\n args: args(`\n type (string) ${_t(\"Account type.\")}\n `),\n returns: [\"NUMBER\"],\n computeFormat: () => \"m/d/yyyy\",\n compute: function (accountType) {\n const accountTypes = this.getters.getAccountGroupCodes(toString(accountType));\n return accountTypes.join(\",\");\n },\n});\n", "/** @odoo-module */\n\nimport { _lt } from \"@web/core/l10n/translation\";\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport AccountingPlugin from \"./plugins/accounting_plugin\";\nimport { getFirstAccountFunction, getNumberOfAccountFormulas } from \"./utils\";\nimport { parseAccountingDate } from \"./accounting_functions\";\nimport { camelToSnakeObject } from \"@spreadsheet/helpers/helpers\";\n\nconst { cellMenuRegistry, featurePluginRegistry } = spreadsheet.registries;\nconst { astToFormula } = spreadsheet;\nconst { toString, toBoolean } = spreadsheet.helpers;\n\nfeaturePluginRegistry.add(\"odooAccountingAggregates\", AccountingPlugin);\n\ncellMenuRegistry.add(\"move_lines_see_records\", {\n name: _lt(\"See records\"),\n sequence: 176,\n async action(env) {\n const position = env.model.getters.getActivePosition();\n const cell = env.model.getters.getCell(position);\n const { args } = getFirstAccountFunction(cell.content);\n let [codes, date_range, offset, companyId, includeUnposted] = args\n .map(astToFormula)\n .map((arg) => env.model.getters.evaluateFormula(arg));\n codes = toString(codes).split(\",\");\n const dateRange = parseAccountingDate(date_range);\n dateRange.year += offset || 0;\n companyId = companyId || null;\n includeUnposted = toBoolean(includeUnposted);\n\n const action = await env.services.orm.call(\n \"account.account\",\n \"spreadsheet_move_line_action\",\n [camelToSnakeObject({ dateRange, companyId, codes, includeUnposted })]\n );\n await env.services.action.doAction(action);\n },\n isVisible: (env) => {\n const position = env.model.getters.getActivePosition();\n const evaluatedCell = env.model.getters.getEvaluatedCell(position);\n const cell = env.model.getters.getCell(position);\n return (\n !evaluatedCell.error &&\n evaluatedCell.value !== \"\" &&\n cell &&\n getNumberOfAccountFormulas(cell.content) === 1\n );\n },\n});\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { AccountingDataSource } from \"../accounting_datasource\";\nconst DATA_SOURCE_ID = \"ACCOUNTING_AGGREGATES\";\n\n/**\n * @typedef {import(\"../accounting_functions\").DateRange} DateRange\n */\n\nexport default class AccountingPlugin extends spreadsheet.UIPlugin {\n constructor(config) {\n super(config);\n this.dataSources = config.custom.dataSources;\n if (this.dataSources) {\n this.dataSources.add(DATA_SOURCE_ID, AccountingDataSource);\n }\n }\n\n // -------------------------------------------------------------------------\n // Getters\n // -------------------------------------------------------------------------\n\n /**\n * Gets the total balance for given account code prefix\n * @param {string[]} codes prefixes of the accounts' codes\n * @param {DateRange} dateRange start date of the period to look\n * @param {number} offset end date of the period to look\n * @param {number | null} companyId specific company to target\n * @param {boolean} includeUnposted wether or not select unposted entries\n * @returns {number}\n */\n getAccountPrefixCredit(codes, dateRange, offset, companyId, includeUnposted) {\n return (\n this.dataSources &&\n this.dataSources\n .get(DATA_SOURCE_ID)\n .getCredit(codes, dateRange, offset, companyId, includeUnposted)\n );\n }\n\n /**\n * Gets the total balance for a given account code prefix\n * @param {string[]} codes prefixes of the accounts codes\n * @param {DateRange} dateRange start date of the period to look\n * @param {number} offset end date of the period to look\n * @param {number | null} companyId specific company to target\n * @param {boolean} includeUnposted wether or not select unposted entries\n * @returns {number}\n */\n getAccountPrefixDebit(codes, dateRange, offset, companyId, includeUnposted) {\n return (\n this.dataSources &&\n this.dataSources\n .get(DATA_SOURCE_ID)\n .getDebit(codes, dateRange, offset, companyId, includeUnposted)\n );\n }\n\n /**\n * @param {Date} date Date included in the fiscal year\n * @param {number | null} companyId specific company to target\n * @returns {string | undefined}\n */\n getFiscalStartDate(date, companyId) {\n return (\n this.dataSources &&\n this.dataSources.get(DATA_SOURCE_ID).getFiscalStartDate(date, companyId)\n );\n }\n\n /**\n * @param {Date} date Date included in the fiscal year\n * @param {number | undefined} companyId specific company to target\n * @returns {string | undefined}\n */\n getFiscalEndDate(date, companyId) {\n return (\n this.dataSources &&\n this.dataSources.get(DATA_SOURCE_ID).getFiscalEndDate(date, companyId)\n );\n }\n\n /**\n * @param {string} accountType\n * @returns {string[]}\n */\n getAccountGroupCodes(accountType) {\n return (\n this.dataSources &&\n this.dataSources.get(DATA_SOURCE_ID).getAccountGroupCodes(accountType)\n );\n }\n}\n\nAccountingPlugin.getters = [\n \"getAccountPrefixCredit\",\n \"getAccountPrefixDebit\",\n \"getAccountGroupCodes\",\n \"getFiscalStartDate\",\n \"getFiscalEndDate\",\n];\n", "/** @odoo-module **/\nimport { getOdooFunctions } from \"@spreadsheet/helpers/odoo_functions_helpers\";\n\n/** @typedef {import(\"@spreadsheet/helpers/odoo_functions_helpers\").OdooFunctionDescription} OdooFunctionDescription*/\n\n/**\n * @param {string} formula\n * @returns {number}\n */\nexport function getNumberOfAccountFormulas(formula) {\n return getOdooFunctions(formula, [\"ODOO.BALANCE\", \"ODOO.CREDIT\", \"ODOO.DEBIT\"]).filter(\n (fn) => fn.isMatched\n ).length;\n}\n\n/**\n * Get the first Account function description of the given formula.\n *\n * @param {string} formula\n * @returns {OdooFunctionDescription | undefined}\n */\nexport function getFirstAccountFunction(formula) {\n return getOdooFunctions(formula, [\"ODOO.BALANCE\", \"ODOO.CREDIT\", \"ODOO.DEBIT\"]).find(\n (fn) => fn.isMatched\n );\n}\n", "/** @odoo-module */\n\nimport { DataSources } from \"@spreadsheet/data_sources/data_sources\";\nimport { migrate } from \"@spreadsheet/o_spreadsheet/migration\";\nimport { download } from \"@web/core/network/download\";\nimport { registry } from \"@web/core/registry\";\nimport spreadsheet from \"../o_spreadsheet/o_spreadsheet_extended\";\nimport { _t } from \"@web/core/l10n/translation\";\n\nconst { Model } = spreadsheet;\n\nasync function downloadSpreadsheet(env, action) {\n const { orm, name, data, stateUpdateMessages } = action.params;\n const dataSources = new DataSources(orm);\n const model = new Model(migrate(data), { custom: { dataSources } }, stateUpdateMessages);\n await waitForDataLoaded(model);\n const { files } = model.exportXLSX();\n await download({\n url: \"/spreadsheet/xlsx\",\n data: {\n zip_name: `${name}.xlsx`,\n files: JSON.stringify(files),\n },\n });\n}\n\n/**\n * Ensure that the spreadsheet does not contains cells that are in loading state\n * @param {Model} model\n * @returns {Promise}\n */\nasync function waitForDataLoaded(model) {\n const dataSources = model.config.custom.dataSources;\n return new Promise((resolve, reject) => {\n function check() {\n model.dispatch(\"EVALUATE_CELLS\");\n if (isLoaded(model)) {\n dataSources.removeEventListener(\"data-source-updated\", check);\n resolve();\n }\n }\n dataSources.addEventListener(\"data-source-updated\", check);\n check();\n });\n}\n\nfunction isLoaded(model) {\n for (const sheetId of model.getters.getSheetIds()) {\n for (const cell of Object.values(model.getters.getEvaluatedCells(sheetId))) {\n if (cell.type === \"error\" && cell.error.message === _t(\"Data is loading\")) {\n return false;\n }\n }\n }\n return true;\n}\n\nregistry\n .category(\"actions\")\n .add(\"action_download_spreadsheet\", downloadSpreadsheet, { force: true });\n", "/** @odoo-module */\n\nimport { OdooViewsDataSource } from \"@spreadsheet/data_sources/odoo_views_data_source\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { GraphModel as ChartModel} from \"@web/views/graph/graph_model\";\n\nexport default class ChartDataSource extends OdooViewsDataSource {\n /**\n * @override\n * @param {Object} services Services (see DataSource)\n */\n constructor(services, params) {\n super(services, params);\n }\n\n /**\n * @protected\n */\n async _load() {\n await super._load();\n const metaData = {\n fieldAttrs: {},\n ...this._metaData,\n };\n this._model = new ChartModel(\n {\n _t,\n },\n metaData,\n {\n orm: this._orm,\n }\n );\n await this._model.load(this._searchParams);\n }\n\n getData() {\n if (!this.isReady()) {\n this.load();\n return { datasets: [], labels: [] };\n }\n if (!this._isValid) {\n return { datasets: [], labels: [] };\n }\n return this._model.data;\n }\n}\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nconst { chartComponentRegistry } = spreadsheet.registries;\nconst { ChartJsComponent } = spreadsheet.components;\n\nchartComponentRegistry.add(\"odoo_bar\", ChartJsComponent);\nchartComponentRegistry.add(\"odoo_line\", ChartJsComponent);\nchartComponentRegistry.add(\"odoo_pie\", ChartJsComponent);\n\nimport OdooChartCorePlugin from \"./plugins/odoo_chart_core_plugin\";\nimport ChartOdooMenuPlugin from \"./plugins/chart_odoo_menu_plugin\";\nimport OdooChartUIPlugin from \"./plugins/odoo_chart_ui_plugin\";\n\nexport { OdooChartCorePlugin, ChartOdooMenuPlugin, OdooChartUIPlugin };\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { OdooChart } from \"./odoo_chart\";\n\nconst { chartRegistry } = spreadsheet.registries;\n\nconst { getDefaultChartJsRuntime, chartFontColor, ChartColors } = spreadsheet.helpers;\n\nexport class OdooBarChart extends OdooChart {\n constructor(definition, sheetId, getters) {\n super(definition, sheetId, getters);\n this.verticalAxisPosition = definition.verticalAxisPosition;\n this.stacked = definition.stacked;\n }\n\n getDefinition() {\n return {\n ...super.getDefinition(),\n verticalAxisPosition: this.verticalAxisPosition,\n stacked: this.stacked,\n };\n }\n}\n\nchartRegistry.add(\"odoo_bar\", {\n match: (type) => type === \"odoo_bar\",\n createChart: (definition, sheetId, getters) => new OdooBarChart(definition, sheetId, getters),\n getChartRuntime: createOdooChartRuntime,\n validateChartDefinition: (validator, definition) =>\n OdooBarChart.validateChartDefinition(validator, definition),\n transformDefinition: (definition) => OdooBarChart.transformDefinition(definition),\n getChartDefinitionFromContextCreation: () => OdooBarChart.getDefinitionFromContextCreation(),\n name: _t(\"Bar\"),\n});\n\nfunction createOdooChartRuntime(chart, getters) {\n const background = chart.background || \"#FFFFFF\";\n const { datasets, labels } = chart.dataSource.getData();\n const chartJsConfig = getBarConfiguration(chart, labels);\n const colors = new ChartColors();\n for (const { label, data } of datasets) {\n const color = colors.next();\n const dataset = {\n label,\n data,\n borderColor: color,\n backgroundColor: color,\n };\n chartJsConfig.data.datasets.push(dataset);\n }\n\n return { background, chartJsConfig };\n}\n\nfunction getBarConfiguration(chart, labels) {\n const fontColor = chartFontColor(chart.background);\n const config = getDefaultChartJsRuntime(chart, labels, fontColor);\n config.type = chart.type.replace(\"odoo_\", \"\");\n const legend = {\n ...config.options.legend,\n display: chart.legendPosition !== \"none\",\n labels: { fontColor },\n };\n legend.position = chart.legendPosition;\n config.options.legend = legend;\n config.options.layout = {\n padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },\n };\n config.options.scales = {\n xAxes: [\n {\n ticks: {\n // x axis configuration\n maxRotation: 60,\n minRotation: 15,\n padding: 5,\n labelOffset: 2,\n fontColor,\n },\n },\n ],\n yAxes: [\n {\n position: chart.verticalAxisPosition,\n ticks: {\n fontColor,\n // y axis configuration\n beginAtZero: true, // the origin of the y axis is always zero\n },\n },\n ],\n };\n if (chart.stacked) {\n config.options.scales.xAxes[0].stacked = true;\n config.options.scales.yAxes[0].stacked = true;\n }\n return config;\n}\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport ChartDataSource from \"../data_source/chart_data_source\";\n\nconst { AbstractChart, CommandResult } = spreadsheet;\n\n/**\n * @typedef {import(\"@web/search/search_model\").SearchParams} SearchParams\n *\n * @typedef MetaData\n * @property {Array} domains\n * @property {Array} groupBy\n * @property {string} measure\n * @property {string} mode\n * @property {string} [order]\n * @property {string} resModel\n * @property {boolean} stacked\n *\n * @typedef OdooChartDefinition\n * @property {string} type\n * @property {MetaData} metaData\n * @property {SearchParams} searchParams\n * @property {string} title\n * @property {string} background\n * @property {string} legendPosition\n *\n * @typedef OdooChartDefinitionDataSource\n * @property {MetaData} metaData\n * @property {SearchParams} searchParams\n *\n */\n\nexport class OdooChart extends AbstractChart {\n /**\n * @param {OdooChartDefinition} definition\n * @param {string} sheetId\n * @param {Object} getters\n */\n constructor(definition, sheetId, getters) {\n super(definition, sheetId, getters);\n this.type = definition.type;\n this.metaData = definition.metaData;\n this.searchParams = definition.searchParams;\n this.legendPosition = definition.legendPosition;\n this.background = definition.background;\n this.dataSource = undefined;\n }\n\n static transformDefinition(definition) {\n return definition;\n }\n\n static validateChartDefinition(validator, definition) {\n return CommandResult.Success;\n }\n\n static getDefinitionFromContextCreation() {\n throw new Error(\"It's not possible to convert an Odoo chart to a native chart\");\n }\n\n /**\n * @returns {OdooChartDefinitionDataSource}\n */\n getDefinitionForDataSource() {\n return {\n metaData: {\n ...this.metaData,\n mode: this.type.replace(\"odoo_\", \"\"),\n },\n searchParams: this.searchParams,\n };\n }\n\n /**\n * @returns {OdooChartDefinition}\n */\n getDefinition() {\n return {\n //@ts-ignore Defined in the parent class\n title: this.title,\n background: this.background,\n legendPosition: this.legendPosition,\n metaData: this.metaData,\n searchParams: this.searchParams,\n type: this.type,\n };\n }\n\n getDefinitionForExcel() {\n // Export not supported\n return undefined;\n }\n\n /**\n * @returns {OdooChart}\n */\n updateRanges() {\n // No range on this graph\n return this;\n }\n\n /**\n * @returns {OdooChart}\n */\n copyForSheetId() {\n return this;\n }\n\n /**\n * @returns {OdooChart}\n */\n copyInSheetId() {\n return this;\n }\n\n getContextCreation() {\n return {};\n }\n\n getSheetIdsUsedInChartRanges() {\n return [];\n }\n\n setDataSource(dataSource) {\n if (dataSource instanceof ChartDataSource) {\n this.dataSource = dataSource;\n }\n else {\n throw new Error(\"Only ChartDataSources can be added.\");\n }\n }\n}\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { OdooChart } from \"./odoo_chart\";\nimport { LINE_FILL_TRANSPARENCY } from \"@web/views/graph/graph_renderer\";\n\nconst { chartRegistry } = spreadsheet.registries;\n\nconst {\n getDefaultChartJsRuntime,\n chartFontColor,\n ChartColors,\n getFillingMode,\n colorToRGBA,\n rgbaToHex,\n} = spreadsheet.helpers;\n\nexport class OdooLineChart extends OdooChart {\n constructor(definition, sheetId, getters) {\n super(definition, sheetId, getters);\n this.verticalAxisPosition = definition.verticalAxisPosition;\n this.stacked = definition.stacked;\n }\n\n getDefinition() {\n return {\n ...super.getDefinition(),\n verticalAxisPosition: this.verticalAxisPosition,\n stacked: this.stacked,\n };\n }\n}\n\nchartRegistry.add(\"odoo_line\", {\n match: (type) => type === \"odoo_line\",\n createChart: (definition, sheetId, getters) => new OdooLineChart(definition, sheetId, getters),\n getChartRuntime: createOdooChartRuntime,\n validateChartDefinition: (validator, definition) =>\n OdooLineChart.validateChartDefinition(validator, definition),\n transformDefinition: (definition) => OdooLineChart.transformDefinition(definition),\n getChartDefinitionFromContextCreation: () => OdooLineChart.getDefinitionFromContextCreation(),\n name: _t(\"Line\"),\n});\n\nfunction createOdooChartRuntime(chart, getters) {\n const background = chart.background || \"#FFFFFF\";\n const { datasets, labels } = chart.dataSource.getData();\n const chartJsConfig = getLineConfiguration(chart, labels);\n const colors = new ChartColors();\n for (const [index, { label, data }] of datasets.entries()) {\n const color = colors.next();\n const backgroundRGBA = colorToRGBA(color);\n if (chart.stacked) {\n // use the transparency of Odoo to keep consistency\n backgroundRGBA.a = LINE_FILL_TRANSPARENCY;\n }\n const backgroundColor = rgbaToHex(backgroundRGBA);\n const dataset = {\n label,\n data,\n lineTension: 0,\n borderColor: color,\n backgroundColor,\n pointBackgroundColor: color,\n fill: chart.stacked ? getFillingMode(index) : false,\n };\n chartJsConfig.data.datasets.push(dataset);\n }\n return { background, chartJsConfig };\n}\n\nfunction getLineConfiguration(chart, labels) {\n const fontColor = chartFontColor(chart.background);\n const config = getDefaultChartJsRuntime(chart, labels, fontColor);\n config.type = chart.type.replace(\"odoo_\", \"\");\n const legend = {\n ...config.options.legend,\n display: chart.legendPosition !== \"none\",\n labels: {\n fontColor,\n generateLabels(chart) {\n const { data } = chart;\n const labels = window.Chart.defaults.global.legend.labels.generateLabels(chart);\n for (const [index, label] of labels.entries()) {\n label.fillStyle = data.datasets[index].borderColor;\n }\n return labels;\n },\n },\n };\n legend.position = chart.legendPosition;\n config.options.legend = legend;\n config.options.layout = {\n padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },\n };\n config.options.scales = {\n xAxes: [\n {\n ticks: {\n // x axis configuration\n maxRotation: 60,\n minRotation: 15,\n padding: 5,\n labelOffset: 2,\n fontColor,\n },\n },\n ],\n yAxes: [\n {\n position: chart.verticalAxisPosition,\n ticks: {\n fontColor,\n // y axis configuration\n beginAtZero: true, // the origin of the y axis is always zero\n },\n },\n ],\n };\n if (chart.stacked) {\n config.options.scales.yAxes[0].stacked = true;\n }\n return config;\n}\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { OdooChart } from \"./odoo_chart\";\n\nconst { chartRegistry } = spreadsheet.registries;\n\nconst { getDefaultChartJsRuntime, chartFontColor, ChartColors } = spreadsheet.helpers;\n\nchartRegistry.add(\"odoo_pie\", {\n match: (type) => type === \"odoo_pie\",\n createChart: (definition, sheetId, getters) => new OdooChart(definition, sheetId, getters),\n getChartRuntime: createOdooChartRuntime,\n validateChartDefinition: (validator, definition) =>\n OdooChart.validateChartDefinition(validator, definition),\n transformDefinition: (definition) => OdooChart.transformDefinition(definition),\n getChartDefinitionFromContextCreation: () => OdooChart.getDefinitionFromContextCreation(),\n name: _t(\"Pie\"),\n});\n\nfunction createOdooChartRuntime(chart, getters) {\n const background = chart.background || \"#FFFFFF\";\n const { datasets, labels } = chart.dataSource.getData();\n const chartJsConfig = getPieConfiguration(chart, labels);\n const colors = new ChartColors();\n for (const { label, data } of datasets) {\n const backgroundColor = getPieColors(colors, datasets);\n const dataset = {\n label,\n data,\n borderColor: \"#FFFFFF\",\n backgroundColor,\n };\n chartJsConfig.data.datasets.push(dataset);\n }\n return { background, chartJsConfig };\n}\n\nfunction getPieConfiguration(chart, labels) {\n const fontColor = chartFontColor(chart.background);\n const config = getDefaultChartJsRuntime(chart, labels, fontColor);\n config.type = chart.type.replace(\"odoo_\", \"\");\n const legend = {\n ...config.options.legend,\n display: chart.legendPosition !== \"none\",\n labels: { fontColor },\n };\n legend.position = chart.legendPosition;\n config.options.legend = legend;\n config.options.layout = {\n padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },\n };\n config.options.tooltips = {\n callbacks: {\n title: function (tooltipItems, data) {\n return data.datasets[tooltipItems[0].datasetIndex].label;\n },\n },\n };\n return config;\n}\n\nfunction getPieColors(colors, dataSetsValues) {\n const pieColors = [];\n const maxLength = Math.max(...dataSetsValues.map((ds) => ds.data.length));\n for (let i = 0; i <= maxLength; i++) {\n pieColors.push(colors.next());\n }\n\n return pieColors;\n}\n", "/** @odoo-module **/\n\nimport { patch } from \"@web/core/utils/patch\";\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { useService } from \"@web/core/utils/hooks\";\n\npatch(spreadsheet.components.ChartFigure.prototype, \"spreadsheet.ChartFigure\", {\n setup() {\n this._super();\n this.menuService = useService(\"menu\");\n this.actionService = useService(\"action\");\n },\n async navigateToOdooMenu() {\n const menu = this.env.model.getters.getChartOdooMenu(this.props.figure.id);\n if (!menu) {\n throw new Error(`Cannot find any menu associated with the chart`);\n }\n await this.actionService.doAction(menu.actionID);\n },\n get hasOdooMenu() {\n return this.env.model.getters.getChartOdooMenu(this.props.figure.id) !== undefined;\n },\n async onClick() {\n if (this.env.isDashboard() && this.hasOdooMenu) {\n this.navigateToOdooMenu();\n }\n },\n});\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nconst { coreTypes } = spreadsheet;\n\n/** Plugin that link charts with Odoo menus. It can contain either the Id of the odoo menu, or its xml id. */\nexport default class ChartOdooMenuPlugin extends spreadsheet.CorePlugin {\n constructor(config) {\n super(config);\n this.odooMenuReference = {};\n }\n\n /**\n * Handle a spreadsheet command\n * @param {Object} cmd Command\n */\n handle(cmd) {\n switch (cmd.type) {\n case \"LINK_ODOO_MENU_TO_CHART\":\n this.history.update(\"odooMenuReference\", cmd.chartId, cmd.odooMenuId);\n break;\n case \"DELETE_FIGURE\":\n this.history.update(\"odooMenuReference\", cmd.id, undefined);\n break;\n }\n }\n\n /**\n * Get odoo menu linked to the chart\n *\n * @param {string} chartId\n * @returns {object | undefined}\n */\n getChartOdooMenu(chartId) {\n const menuId = this.odooMenuReference[chartId];\n return menuId ? this.getters.getIrMenu(menuId) : undefined;\n }\n\n import(data) {\n if (data.chartOdooMenusReferences) {\n this.odooMenuReference = data.chartOdooMenusReferences;\n }\n }\n\n export(data) {\n data.chartOdooMenusReferences = this.odooMenuReference;\n }\n}\nChartOdooMenuPlugin.getters = [\"getChartOdooMenu\"];\n\ncoreTypes.add(\"LINK_ODOO_MENU_TO_CHART\");\n", "/** @odoo-module */\nimport spreadsheet from \"../../o_spreadsheet/o_spreadsheet_extended\";\nimport ChartDataSource from \"../data_source/chart_data_source\";\nimport { globalFiltersFieldMatchers } from \"@spreadsheet/global_filters/plugins/global_filters_core_plugin\";\nimport { sprintf } from \"@web/core/utils/strings\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { checkFilterFieldMatching } from \"@spreadsheet/global_filters/helpers\";\nimport CommandResult from \"../../o_spreadsheet/cancelled_reason\";\n\nconst { CorePlugin } = spreadsheet;\n\n/**\n * @typedef {Object} Chart\n * @property {string} dataSourceId\n * @property {Object} fieldMatching\n *\n * @typedef {import(\"@spreadsheet/global_filters/plugins/global_filters_core_plugin\").FieldMatching} FieldMatching\n */\n\nexport default class OdooChartCorePlugin extends CorePlugin {\n constructor(config) {\n super(config);\n this.dataSources = config.custom.dataSources;\n\n /** @type {Object.} */\n this.charts = {};\n\n globalFiltersFieldMatchers[\"chart\"] = {\n geIds: () => this.getters.getOdooChartIds(),\n getDisplayName: (chartId) => this.getters.getOdooChartDisplayName(chartId),\n getTag: async (chartId) => {\n const model = await this.getChartDataSource(chartId).getModelLabel();\n return sprintf(_t(\"Chart - %s\"), model);\n },\n getFieldMatching: (chartId, filterId) =>\n this.getOdooChartFieldMatching(chartId, filterId),\n waitForReady: () => this.getOdooChartsWaitForReady(),\n getModel: (chartId) =>\n this.getters.getChart(chartId).getDefinitionForDataSource().metaData.resModel,\n getFields: (chartId) => this.getChartDataSource(chartId).getFields(),\n };\n }\n\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"ADD_GLOBAL_FILTER\":\n case \"EDIT_GLOBAL_FILTER\":\n if (cmd.chart) {\n return checkFilterFieldMatching(cmd.chart);\n }\n }\n return CommandResult.Success;\n }\n\n /**\n * Handle a spreadsheet command\n *\n * @param {Object} cmd Command\n */\n handle(cmd) {\n switch (cmd.type) {\n case \"CREATE_CHART\": {\n switch (cmd.definition.type) {\n case \"odoo_pie\":\n case \"odoo_bar\":\n case \"odoo_line\":\n this._addOdooChart(cmd.id);\n break;\n }\n break;\n }\n case \"UPDATE_CHART\": {\n switch (cmd.definition.type) {\n case \"odoo_pie\":\n case \"odoo_bar\":\n case \"odoo_line\":\n this._setChartDataSource(cmd.id);\n break;\n }\n break;\n }\n case \"DELETE_FIGURE\": {\n const charts = { ...this.charts };\n delete charts[cmd.id];\n this.history.update(\"charts\", charts);\n break;\n }\n case \"REMOVE_GLOBAL_FILTER\":\n this._onFilterDeletion(cmd.id);\n break;\n case \"ADD_GLOBAL_FILTER\":\n case \"EDIT_GLOBAL_FILTER\":\n if (cmd.chart) {\n this._setOdooChartFieldMatching(cmd.filter.id, cmd.chart);\n }\n break;\n }\n }\n\n // -------------------------------------------------------------------------\n // Getters\n // -------------------------------------------------------------------------\n\n /**\n * Get all the odoo chart ids\n * @returns {Array}\n */\n getOdooChartIds() {\n const ids = [];\n for (const sheetId of this.getters.getSheetIds()) {\n ids.push(\n ...this.getters\n .getChartIds(sheetId)\n .filter((id) => this.getters.getChartType(id).startsWith(\"odoo_\"))\n );\n }\n return ids;\n }\n\n /**\n * @param {string} chartId\n * @returns {string}\n */\n getChartFieldMatch(chartId) {\n return this.charts[chartId].fieldMatching;\n }\n\n /**\n * @param {string} id\n * @returns {ChartDataSource|undefined}\n */\n getChartDataSource(id) {\n const dataSourceId = this.charts[id].dataSourceId;\n return this.dataSources.get(dataSourceId);\n }\n\n /**\n *\n * @param {string} chartId\n * @returns {string}\n */\n getOdooChartDisplayName(chartId) {\n return this.getters.getChart(chartId).title;\n }\n\n /**\n * Import the pivots\n *\n * @param {Object} data\n */\n import(data) {\n for (const sheet of data.sheets) {\n if (sheet.figures) {\n for (const figure of sheet.figures) {\n if (figure.tag === \"chart\" && figure.data.type.startsWith(\"odoo_\")) {\n this._addOdooChart(figure.id, figure.data.fieldMatching);\n }\n }\n }\n }\n }\n /**\n * Export the pivots\n *\n * @param {Object} data\n */\n export(data) {\n for (const sheet of data.sheets) {\n if (sheet.figures) {\n for (const figure of sheet.figures) {\n if (figure.tag === \"chart\" && figure.data.type.startsWith(\"odoo_\")) {\n figure.data.fieldMatching = this.getChartFieldMatch(figure.id);\n }\n }\n }\n }\n }\n // -------------------------------------------------------------------------\n // Private\n // -------------------------------------------------------------------------\n\n /**\n *\n * @return {Promise[]}\n */\n getOdooChartsWaitForReady() {\n return this.getOdooChartIds().map((chartId) =>\n this.getChartDataSource(chartId).loadMetadata()\n );\n }\n\n /**\n * Get the current pivotFieldMatching of a chart\n *\n * @param {string} chartId\n * @param {string} filterId\n */\n getOdooChartFieldMatching(chartId, filterId) {\n return this.charts[chartId].fieldMatching[filterId];\n }\n\n /**\n * Sets the current pivotFieldMatching of a chart\n *\n * @param {string} filterId\n * @param {Record} chartFieldMatches\n */\n _setOdooChartFieldMatching(filterId, chartFieldMatches) {\n const charts = { ...this.charts };\n for (const [chartId, fieldMatch] of Object.entries(chartFieldMatches)) {\n charts[chartId].fieldMatching[filterId] = fieldMatch;\n }\n this.history.update(\"charts\", charts);\n }\n\n _onFilterDeletion(filterId) {\n const charts = { ...this.charts };\n for (const chartId in charts) {\n this.history.update(\"charts\", chartId, \"fieldMatching\", filterId, undefined);\n }\n }\n\n /**\n * @param {string} chartId\n * @param {string} dataSourceId\n */\n _addOdooChart(chartId, fieldMatching = {}) {\n const dataSourceId = this.uuidGenerator.uuidv4();\n const charts = { ...this.charts };\n charts[chartId] = {\n dataSourceId,\n fieldMatching,\n };\n const definition = this.getters.getChart(chartId).getDefinitionForDataSource();\n if (!this.dataSources.contains(dataSourceId)) {\n this.dataSources.add(dataSourceId, ChartDataSource, definition);\n }\n this.history.update(\"charts\", charts);\n this._setChartDataSource(chartId);\n }\n\n /**\n * Sets the catasource on the corresponding chart\n * @param {string} chartId\n */\n _setChartDataSource(chartId) {\n const chart = this.getters.getChart(chartId);\n chart.setDataSource(this.getters.getChartDataSource(chartId));\n }\n}\n\nOdooChartCorePlugin.getters = [\n \"getChartDataSource\",\n \"getOdooChartIds\",\n \"getChartFieldMatch\",\n \"getOdooChartDisplayName\",\n \"getOdooChartFieldMatching\",\n];\n", "/** @odoo-module */\n\nimport spreadsheet from \"../../o_spreadsheet/o_spreadsheet_extended\";\nimport { Domain } from \"@web/core/domain\";\n\nconst { UIPlugin } = spreadsheet;\n\nexport default class OdooChartUIPlugin extends UIPlugin {\n beforeHandle(cmd) {\n switch (cmd.type) {\n case \"START\":\n // make sure the domains are correctly set before\n // any evaluation\n this._addDomains();\n break;\n }\n }\n\n /**\n * Handle a spreadsheet command\n *\n * @param {Object} cmd Command\n */\n handle(cmd) {\n switch (cmd.type) {\n case \"ADD_GLOBAL_FILTER\":\n case \"EDIT_GLOBAL_FILTER\":\n case \"REMOVE_GLOBAL_FILTER\":\n case \"SET_GLOBAL_FILTER_VALUE\":\n case \"CLEAR_GLOBAL_FILTER_VALUE\":\n this._addDomains();\n break;\n case \"UNDO\":\n case \"REDO\":\n if (\n cmd.commands.find((command) =>\n [\n \"ADD_GLOBAL_FILTER\",\n \"EDIT_GLOBAL_FILTER\",\n \"REMOVE_GLOBAL_FILTER\",\n ].includes(command.type)\n )\n ) {\n this._addDomains();\n }\n break;\n }\n }\n\n // -------------------------------------------------------------------------\n // Private\n // -------------------------------------------------------------------------\n\n /**\n * Add an additional domain to a chart\n *\n * @private\n *\n * @param {string} chartId chart id\n */\n _addDomain(chartId) {\n const domainList = [];\n for (const [filterId, fieldMatch] of Object.entries(\n this.getters.getChartFieldMatch(chartId)\n )) {\n domainList.push(this.getters.getGlobalFilterDomain(filterId, fieldMatch));\n }\n const domain = Domain.combine(domainList, \"AND\").toString();\n this.getters.getChartDataSource(chartId).addDomain(domain);\n }\n\n /**\n * Add an additional domain to all chart\n *\n * @private\n *\n */\n _addDomains() {\n for (const chartId of this.getters.getOdooChartIds()) {\n this._addDomain(chartId);\n }\n }\n}\n\nOdooChartUIPlugin.getters = [];\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nconst { inverseCommandRegistry, otRegistry } = spreadsheet.registries;\n\nfunction identity(cmd) {\n return [cmd];\n}\n\notRegistry.addTransformation(\n \"DELETE_FIGURE\",\n [\"LINK_ODOO_MENU_TO_CHART\"],\n (toTransform, executed) => {\n if (executed.id === toTransform.chartId) {\n return undefined;\n }\n return toTransform;\n }\n);\n\ninverseCommandRegistry.add(\"LINK_ODOO_MENU_TO_CHART\", identity);\n", "/** @odoo-module */\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport { ServerData } from \"../data_sources/server_data\";\n\n/**\n * @typedef Currency\n * @property {string} name\n * @property {string} code\n * @property {string} symbol\n * @property {number} decimalPlaces\n * @property {\"before\" | \"after\"} position\n */\nexport class CurrencyDataSource {\n constructor(services) {\n this.serverData = new ServerData(services.orm, {\n whenDataIsFetched: () => services.notify(),\n });\n }\n\n /**\n * Get the currency rate between the two given currencies\n * @param {string} from Currency from\n * @param {string} to Currency to\n * @param {string|undefined} date\n * @returns {number|undefined}\n */\n getCurrencyRate(from, to, date) {\n const data = this.serverData.batch.get(\"res.currency.rate\", \"get_rates_for_spreadsheet\", {\n from,\n to,\n date,\n });\n const rate = data !== undefined ? data.rate : undefined;\n if (rate === false) {\n throw new Error(_t(\"Currency rate unavailable.\"));\n }\n return rate;\n }\n\n /**\n *\n * @param {number|undefined} companyId\n * @returns {Currency}\n */\n getCompanyCurrencyFormat(companyId) {\n const result = this.serverData.get(\"res.currency\", \"get_company_currency_for_spreadsheet\", [\n companyId,\n ]);\n if (result === false) {\n throw new Error(_t(\"Currency not available for this company.\"));\n }\n return result;\n }\n\n /**\n * Get all currencies from the server\n * @param {string} currencyName\n * @returns {Currency}\n */\n getCurrency(currencyName) {\n return this.serverData.batch.get(\n \"res.currency\",\n \"get_currencies_for_spreadsheet\",\n currencyName\n );\n }\n}\n", "/** @odoo-module **/\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport spreadsheet from \"../o_spreadsheet/o_spreadsheet_extended\";\nconst { args, toString, toJSDate } = spreadsheet.helpers;\nconst { functionRegistry } = spreadsheet.registries;\n\n\nfunctionRegistry\n .add(\"ODOO.CURRENCY.RATE\", {\n description: _t(\"This function takes in two currency codes as arguments, and returns the exchange rate from the first currency to the second as float.\"),\n compute: function (currencyFrom, currencyTo, date) {\n const from = toString(currencyFrom);\n const to = toString(currencyTo);\n const _date = date ? toJSDate(date) : undefined;\n return this.getters.getCurrencyRate(from, to, _date);\n },\n args: args(`\n currency_from (string) ${_t(\"First currency code.\")}\n currency_to (string) ${_t(\"Second currency code.\")}\n date (date, optional) ${_t(\"Date of the rate.\")}\n `),\n returns: [\"NUMBER\"],\n});\n", "/** @odoo-module */\n\nimport spreadsheet from \"../../o_spreadsheet/o_spreadsheet_extended\";\nimport { CurrencyDataSource } from \"../currency_data_source\";\nconst { featurePluginRegistry } = spreadsheet.registries;\n\nconst DATA_SOURCE_ID = \"CURRENCIES\";\n\n/**\n * @typedef {import(\"../currency_data_source\").Currency} Currency\n */\n\nclass CurrencyPlugin extends spreadsheet.UIPlugin {\n constructor(config) {\n super(config);\n this.dataSources = config.custom.dataSources;\n if (this.dataSources) {\n this.dataSources.add(DATA_SOURCE_ID, CurrencyDataSource);\n }\n }\n\n // -------------------------------------------------------------------------\n // Getters\n // -------------------------------------------------------------------------\n\n /**\n * Get the currency rate between the two given currencies\n * @param {string} from Currency from\n * @param {string} to Currency to\n * @param {string} date\n * @returns {number|string}\n */\n getCurrencyRate(from, to, date) {\n return (\n this.dataSources && this.dataSources.get(DATA_SOURCE_ID).getCurrencyRate(from, to, date)\n );\n }\n\n /**\n *\n * @param {Currency | undefined} currency\n * @private\n *\n * @returns {string | undefined}\n */\n computeFormatFromCurrency(currency) {\n if (!currency) {\n return undefined;\n }\n const decimalFormatPart = currency.decimalPlaces\n ? \".\" + \"0\".repeat(currency.decimalPlaces)\n : \"\";\n const numberFormat = \"#,##0\" + decimalFormatPart;\n const symbolFormatPart = \"[$\" + currency.symbol + \"]\";\n return currency.position === \"after\"\n ? numberFormat + symbolFormatPart\n : symbolFormatPart + numberFormat;\n }\n\n /**\n * Returns the default display format of a given currency\n * @param {string} currencyName\n * @returns {string | undefined}\n */\n getCurrencyFormat(currencyName) {\n const currency =\n currencyName &&\n this.dataSources &&\n this.dataSources.get(DATA_SOURCE_ID).getCurrency(currencyName);\n return this.computeFormatFromCurrency(currency);\n }\n\n /**\n * Returns the default display format of a the company currency\n * @param {number|undefined} companyId\n * @returns {string | undefined}\n */\n getCompanyCurrencyFormat(companyId) {\n const currency =\n this.dataSources &&\n this.dataSources.get(DATA_SOURCE_ID).getCompanyCurrencyFormat(companyId);\n return this.computeFormatFromCurrency(currency);\n }\n}\n\nCurrencyPlugin.getters = [\"getCurrencyRate\", \"getCurrencyFormat\", \"getCompanyCurrencyFormat\"];\n\nfeaturePluginRegistry.add(\"odooCurrency\", CurrencyPlugin);\n", "/** @odoo-module */\n\nimport { LoadingDataError } from \"@spreadsheet/o_spreadsheet/errors\";\nimport { RPCError } from \"@web/core/network/rpc_service\";\nimport { KeepLast } from \"@web/core/utils/concurrency\";\n\n/**\n * DataSource is an abstract class that contains the logic of fetching and\n * maintaining access to data that have to be loaded.\n *\n * A class which extends this class have to implement the `_load` method\n * * which should load the data it needs\n *\n * Subclass can implement concrete methods to have access to a\n * particular data.\n */\nexport class LoadableDataSource {\n constructor(services) {\n this._orm = services.orm;\n this._metadataRepository = services.metadataRepository;\n this._notify = services.notify;\n\n /**\n * Last time that this dataSource has been updated\n */\n this._lastUpdate = undefined;\n\n this._concurrency = new KeepLast();\n /**\n * Promise to control the loading of data\n */\n this._loadPromise = undefined;\n this._isFullyLoaded = false;\n this._isValid = true;\n this._loadErrorMessage = \"\";\n }\n\n /**\n * Load data in the model\n * @param {object} [params] Params for fetching data\n * @param {boolean} [params.reload=false] Force the reload of the data\n *\n * @returns {Promise} Resolved when data are fetched.\n */\n async load(params) {\n if (params && params.reload) {\n this._loadPromise = undefined;\n }\n if (!this._loadPromise) {\n this._isFullyLoaded = false;\n this._isValid = true;\n this._loadErrorMessage = \"\";\n this._loadPromise = this._concurrency\n .add(this._load())\n .catch((e) => {\n this._isValid = false;\n this._loadErrorMessage = e instanceof RPCError ? e.data.message : e.message;\n })\n .finally(() => {\n this._lastUpdate = Date.now();\n this._isFullyLoaded = true;\n this._notify();\n });\n }\n return this._loadPromise;\n }\n\n get lastUpdate() {\n return this._lastUpdate;\n }\n\n /**\n * @returns {boolean}\n */\n isReady() {\n return this._isFullyLoaded;\n }\n\n /**\n * @protected\n */\n _assertDataIsLoaded() {\n if (!this._isFullyLoaded) {\n this.load();\n throw new LoadingDataError();\n }\n if (!this._isValid) {\n throw new Error(this._loadErrorMessage);\n }\n }\n\n /**\n * Load the data in the model\n *\n * @abstract\n * @protected\n */\n async _load() {}\n}\n", "/** @odoo-module */\n\nimport { LoadableDataSource } from \"./data_source\";\nimport { MetadataRepository } from \"./metadata_repository\";\n\nconst { EventBus } = owl;\n\n/** *\n * @typedef {object} DataSourceServices\n * @property {MetadataRepository} metadataRepository\n * @property {import(\"@web/core/orm_service\")} orm\n * @property {() => void} notify\n *\n * @typedef {new (services: DataSourceServices, params: object) => any} DataSourceConstructor\n */\n\nexport class DataSources extends EventBus {\n constructor(orm) {\n super();\n this._orm = orm.silent;\n this._metadataRepository = new MetadataRepository(orm);\n this._metadataRepository.addEventListener(\"labels-fetched\", () => this.notify());\n /** @type {Object.} */\n this._dataSources = {};\n }\n\n /**\n * Create a new data source but do not register it.\n *\n * @param {DataSourceConstructor} cls Class to instantiate\n * @param {object} params Params to give to data source\n *\n * @returns {any}\n */\n create(cls, params) {\n return new cls(\n {\n orm: this._orm,\n metadataRepository: this._metadataRepository,\n notify: () => this.notify(),\n },\n params\n );\n }\n\n /**\n * Create a new data source and register it with the following id.\n *\n * @param {string} id\n * @param {DataSourceConstructor} cls Class to instantiate\n * @param {object} params Params to give to data source\n *\n * @returns {any}\n */\n add(id, cls, params) {\n this._dataSources[id] = this.create(cls, params);\n return this._dataSources[id];\n }\n\n async load(id, reload = false) {\n const dataSource = this.get(id);\n if (dataSource instanceof LoadableDataSource) {\n await dataSource.load({ reload });\n }\n }\n\n /**\n * Retrieve the data source with the following id.\n *\n * @param {string} id\n *\n * @returns {any}\n */\n get(id) {\n return this._dataSources[id];\n }\n\n /**\n * Check if the following is correspond to a data source.\n *\n * @param {string} id\n *\n * @returns {boolean}\n */\n contains(id) {\n return id in this._dataSources;\n }\n\n /**\n * Notify that a data source has been updated. Could be useful to\n * request a re-evaluation.\n */\n notify() {\n this.trigger(\"data-source-updated\");\n }\n\n async waitForAllLoaded() {\n await Promise.all(\n Object.values(this._dataSources).map(\n (ds) => ds instanceof LoadableDataSource && ds.load()\n )\n );\n }\n}\n", "/** @odoo-module */\n\nimport { Deferred } from \"@web/core/utils/concurrency\";\nimport { LoadingDataError } from \"../o_spreadsheet/errors\";\nimport BatchEndpoint, { Request } from \"./server_data\";\n\n/**\n * @typedef PendingDisplayName\n * @property {\"PENDING\"} state\n * @property {Deferred} deferred\n *\n * @typedef ErrorDisplayName\n * @property {\"ERROR\"} state\n * @property {Deferred} deferred\n * @property {Error} error\n *\n * @typedef CompletedDisplayName\n * @property {\"COMPLETED\"} state\n * @property {Deferred} deferred\n * @property {string|undefined} value\n *\n * @typedef {PendingDisplayName | ErrorDisplayName | CompletedDisplayName} DisplayNameResult\n *\n * @typedef {[number, string]} BatchedNameGetRPCResult\n */\n\n/**\n * This class is responsible for fetching the display names of records. It\n * caches the display names of records that have already been fetched.\n * It also provides a way to wait for the display name of a record to be\n * fetched.\n */\nexport class DisplayNameRepository {\n /**\n *\n * @param {import(\"@web/core/orm_service\").ORM} orm\n * @param {Object} params\n * @param {function} params.whenDataIsFetched Callback to call when the\n * display name of a record is fetched.\n */\n constructor(orm, { whenDataIsFetched }) {\n this.dataFetchedCallback = whenDataIsFetched;\n /**\n * Contains the display names of records. It's organized in the following way:\n * {\n * \"res.country\": {\n * 1: {\n * \"value\": \"Belgium\",\n * \"deferred\": Deferred<\"Belgium\">,\n * },\n * }\n */\n /** @type {Object.>}*/\n this._displayNames = {};\n this._orm = orm;\n this._endpoints = {};\n }\n\n /**\n * Get the display name of the given record.\n *\n * @param {string} model\n * @param {number} id\n * @returns {Promise}\n */\n async getDisplayNameAsync(model, id) {\n const displayNameResult = this._displayNames[model] && this._displayNames[model][id];\n if (!displayNameResult) {\n return this._fetchDisplayName(model, id);\n }\n return displayNameResult.deferred;\n }\n\n /**\n * Set the display name of the given record. This will prevent the display name\n * from being fetched in the background.\n *\n * @param {string} model\n * @param {number} id\n * @param {string} displayName\n */\n setDisplayName(model, id, displayName) {\n if (!this._displayNames[model]) {\n this._displayNames[model] = {};\n }\n const deferred = new Deferred();\n deferred.resolve(displayName);\n this._displayNames[model][id] = {\n state: \"COMPLETED\",\n deferred,\n value: displayName,\n };\n }\n\n /**\n * Get the display name of the given record. If the record does not exist,\n * it will throw a LoadingDataError and fetch the display name in the background.\n *\n * @param {string} model\n * @param {number} id\n * @returns {string}\n */\n getDisplayName(model, id) {\n const displayNameResult = this._displayNames[model] && this._displayNames[model][id];\n if (!displayNameResult) {\n // Catch the error to prevent the error from being thrown in the\n // background.\n this._fetchDisplayName(model, id).catch(() => {});\n throw new LoadingDataError();\n }\n switch (displayNameResult.state) {\n case \"ERROR\":\n throw displayNameResult.error;\n case \"COMPLETED\":\n return displayNameResult.value;\n default:\n throw new LoadingDataError();\n }\n }\n\n /**\n * Get the batch endpoint for the given model. If it does not exist, it will\n * be created.\n *\n * @param {string} model\n * @returns {BatchEndpoint}\n */\n _getEndpoint(model) {\n if (!this._endpoints[model]) {\n this._endpoints[model] = new BatchEndpoint(this._orm, model, \"name_get\", {\n whenDataIsFetched: () => this.dataFetchedCallback(),\n successCallback: this._assignResult.bind(this),\n failureCallback: this._assignError.bind(this),\n });\n }\n return this._endpoints[model];\n }\n\n /**\n * This method is called when the display name of a record is successfully\n * fetched. It updates the cache and resolves the deferred of the record.\n *\n * @param {Request} request\n * @param {BatchedNameGetRPCResult} result\n *\n * @private\n */\n _assignResult(request, result) {\n const deferred = this._displayNames[request.resModel][request.args[0]].deferred;\n deferred.resolve(result && result[1]);\n this._displayNames[request.resModel][request.args[0]] = {\n state: \"COMPLETED\",\n deferred,\n value: result && result[1],\n };\n }\n\n /**\n * This method is called when the display name of a record could not be\n * fetched. It updates the cache and rejects the deferred of the record.\n *\n * @param {Request} request\n * @param {Error} error\n *\n * @private\n */\n _assignError(request, error) {\n const deferred = this._displayNames[request.resModel][request.args[0]].deferred;\n deferred.reject(error);\n this._displayNames[request.resModel][request.args[0]] = {\n state: \"ERROR\",\n deferred,\n error,\n };\n }\n\n /**\n * This method is called when the display name of a record is not in the\n * cache. It creates a deferred and fetches the display name in the\n * background.\n *\n * @param {string} model\n * @param {number} id\n *\n * @private\n * @returns {Deferred}\n */\n async _fetchDisplayName(model, id) {\n const deferred = new Deferred();\n if (!this._displayNames[model]) {\n this._displayNames[model] = {};\n }\n this._displayNames[model][id] = {\n state: \"PENDING\",\n deferred,\n };\n const endpoint = this._getEndpoint(model);\n const request = new Request(model, \"name_get\", [id]);\n endpoint.call(request);\n return deferred;\n }\n}\n", "/** @odoo-module */\n\n/**\n * This class is responsible for keeping track of the labels of records. It\n * caches the labels of records that have already been fetched.\n * This class will not fetch the labels of records, it is the responsibility of\n * the caller to fetch the labels and insert them in this repository.\n */\nexport class LabelsRepository {\n constructor() {\n /**\n * Contains the labels of records. It's organized in the following way:\n * {\n * \"crm.lead\": {\n * \"city\": {\n * \"bruxelles\": \"Bruxelles\",\n * }\n * },\n * }\n */\n this._labels = {};\n }\n\n /**\n * Get the label of a record.\n * @param {string} model technical name of the model\n * @param {string} field name of the field\n * @param {any} value value of the field\n *\n * @returns {string|undefined} label of the record\n */\n getLabel(model, field, value) {\n return (\n this._labels[model] && this._labels[model][field] && this._labels[model][field][value]\n );\n }\n\n /**\n * Set the label of a record.\n * @param {string} model\n * @param {string} field\n * @param {string|number} value\n * @param {string|undefined} label\n */\n setLabel(model, field, value, label) {\n if (!this._labels[model]) {\n this._labels[model] = {};\n }\n if (!this._labels[model][field]) {\n this._labels[model][field] = {};\n }\n this._labels[model][field][value] = label;\n }\n}\n", "/** @odoo-module */\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport { sprintf } from \"@web/core/utils/strings\";\nimport { ServerData } from \"../data_sources/server_data\";\n\nimport { LoadingDataError } from \"../o_spreadsheet/errors\";\nimport { DisplayNameRepository } from \"./display_name_repository\";\nimport { LabelsRepository } from \"./labels_repository\";\n\nconst { EventBus } = owl;\n\n/**\n * @typedef {object} Field\n * @property {string} name technical name\n * @property {string} type field type\n * @property {string} string display name\n * @property {string} [relation] related model technical name (only for relational fields)\n * @property {boolean} [searchable] true if a field can be searched in database\n */\n/**\n * This class is used to provide facilities to fetch some common data. It's\n * used in the data sources to obtain the fields (fields_get) and the display\n * name of the models (display_name_for on ir.model).\n *\n * It also manages the labels of all the spreadsheet models (labels of basic\n * fields or display name of relational fields).\n *\n * All the results are cached in order to avoid useless rpc calls, basically\n * for different entities that are defined on the same model.\n *\n * Implementation note:\n * For the labels, when someone is asking for a display name which is not loaded yet,\n * the proxy returns directly (undefined) and a request for a name_get will\n * be triggered. All the requests created are batched and send, with only one\n * request per model, after a clock cycle.\n * At the end of this process, an event is triggered (labels-fetched)\n */\nexport class MetadataRepository extends EventBus {\n constructor(orm) {\n super();\n this.orm = orm;\n\n this.serverData = new ServerData(this.orm, {\n whenDataIsFetched: () => this.trigger(\"labels-fetched\"),\n });\n\n this.labelsRepository = new LabelsRepository();\n\n this.displayNameRepository = new DisplayNameRepository(this.orm, {\n whenDataIsFetched: () => this.trigger(\"labels-fetched\"),\n });\n }\n\n /**\n * Get the display name of the given model\n *\n * @param {string} model Technical name\n * @returns {Promise} Display name of the model\n */\n async modelDisplayName(model) {\n const result = await this.serverData.fetch(\"ir.model\", \"display_name_for\", [[model]]);\n return (result[0] && result[0].display_name) || \"\";\n }\n\n /**\n * Get the list of fields for the given model\n *\n * @param {string} model Technical name\n * @returns {Promise>} List of fields (result of fields_get)\n */\n async fieldsGet(model) {\n return this.serverData.fetch(model, \"fields_get\");\n }\n\n /**\n * Add a label to the cache\n *\n * @param {string} model\n * @param {string} field\n * @param {any} value\n * @param {string} label\n */\n registerLabel(model, field, value, label) {\n this.labelsRepository.setLabel(model, field, value, label);\n }\n\n /**\n * Get the label associated with the given arguments\n *\n * @param {string} model\n * @param {string} field\n * @param {any} value\n * @returns {string}\n */\n getLabel(model, field, value) {\n return this.labelsRepository.getLabel(model, field, value);\n }\n\n /**\n * Save the result of a name_get request in the cache\n */\n setDisplayName(model, id, result) {\n this.displayNameRepository.setDisplayName(model, id, result);\n }\n\n /**\n * Get the display name associated to the given model-id\n * If the name is not yet loaded, a rpc will be triggered in the next clock\n * cycle.\n *\n * @param {string} model\n * @param {number} id\n * @returns {string}\n */\n getRecordDisplayName(model, id) {\n try {\n return this.displayNameRepository.getDisplayName(model, id);\n } catch (e) {\n if (e instanceof LoadingDataError) {\n throw e;\n }\n throw new Error(sprintf(_t(\"Unable to fetch the label of %s of model %s\"), id, model));\n }\n }\n}\n", "/** @odoo-module */\n\nimport { LoadableDataSource } from \"./data_source\";\nimport { Domain } from \"@web/core/domain\";\nimport { LoadingDataError } from \"@spreadsheet/o_spreadsheet/errors\";\nimport { omit } from \"@web/core/utils/objects\";\n\n/**\n * @typedef {import(\"@spreadsheet/data_sources/metadata_repository\").Field} Field\n */\n\n/**\n * @typedef {Object} OdooModelMetaData\n * @property {string} resModel\n * @property {Array|undefined} fields\n */\n\nexport class OdooViewsDataSource extends LoadableDataSource {\n /**\n * @override\n * @param {Object} services\n * @param {Object} params\n * @param {OdooModelMetaData} params.metaData\n * @param {Object} params.searchParams\n */\n constructor(services, params) {\n super(services);\n this._metaData = JSON.parse(JSON.stringify(params.metaData));\n /** @protected */\n this._initialSearchParams = JSON.parse(JSON.stringify(params.searchParams));\n this._initialSearchParams.context = omit(\n this._initialSearchParams.context || {},\n ...Object.keys(this._orm.user.context)\n );\n /** @private */\n this._customDomain = this._initialSearchParams.domain;\n }\n\n /**\n * @protected\n */\n get _searchParams() {\n return {\n ...this._initialSearchParams,\n domain: this._customDomain,\n };\n }\n\n async loadMetadata() {\n if (!this._metaData.fields) {\n this._metaData.fields = await this._metadataRepository.fieldsGet(\n this._metaData.resModel\n );\n }\n }\n\n /**\n * @returns {Record} List of fields\n */\n getFields() {\n if (this._metaData.fields === undefined) {\n this.loadMetadata();\n throw new LoadingDataError();\n }\n return this._metaData.fields;\n }\n\n /**\n * @param {string} field Field name\n * @returns {Field | undefined} Field\n */\n getField(field) {\n return this._metaData.fields[field];\n }\n\n /**\n * @protected\n */\n async _load() {\n await this.loadMetadata();\n }\n\n isMetaDataLoaded() {\n return this._metaData.fields !== undefined;\n }\n\n /**\n * Get the computed domain of this source\n * @returns {Array}\n */\n getComputedDomain() {\n return this._customDomain;\n }\n\n addDomain(domain) {\n const newDomain = Domain.and([this._initialSearchParams.domain, domain]);\n if (newDomain.toString() === new Domain(this._customDomain).toString()) {\n return;\n }\n this._customDomain = newDomain.toList();\n if (this._loadPromise === undefined) {\n // if the data source has never been loaded, there's no point\n // at reloading it now.\n return;\n }\n this.load({ reload: true });\n }\n\n /**\n * @returns {Promise} Display name of the model\n */\n getModelLabel() {\n return this._metadataRepository.modelDisplayName(this._metaData.resModel);\n }\n}\n", "/** @odoo-module */\nimport { LoadingDataError } from \"../o_spreadsheet/errors\";\n\n/**\n * @param {T[]} array\n * @returns {T[]}\n * @template T\n */\nfunction removeDuplicates(array) {\n return [...new Set(array.map((el) => JSON.stringify(el)))].map((el) => JSON.parse(el));\n}\n\nexport class Request {\n /**\n * @param {string} resModel\n * @param {string} method\n * @param {unknown[]} args\n */\n constructor(resModel, method, args) {\n this.resModel = resModel;\n this.method = method;\n this.args = args;\n this.key = `${resModel}/${method}(${JSON.stringify(args)})`;\n }\n}\n\n/**\n * A batch request consists of multiple requests which are combined into a single RPC.\n *\n * The batch responsibility is to combine individual requests into a single RPC payload\n * and to split the response back for individual requests.\n *\n * The server method must have the following API:\n * - The input is a list of arguments. Each list item being the arguments of a single request.\n * - The output is a list of results, ordered according to the input list\n *\n * ```\n * [result1, result2] = self.env['my.model'].my_batched_method([request_1_args, request_2_args])\n * ```\n */\nclass ListRequestBatch {\n /**\n * @param {string} resModel\n * @param {string} method\n * @param {Request[]} requests\n */\n constructor(resModel, method, requests = []) {\n this.resModel = resModel;\n this.method = method;\n this.requests = requests;\n }\n\n get payload() {\n const payload = removeDuplicates(this.requests.map((request) => request.args).flat());\n return [payload];\n }\n\n /**\n * @param {Request} request\n */\n add(request) {\n if (request.resModel !== this.resModel || request.method !== this.method) {\n throw new Error(\n `Request ${request.resModel}/${request.method} cannot be added to the batch ${this.resModel}/${this.method}`\n );\n }\n this.requests.push(request);\n }\n\n /**\n * Split the batched RPC response into single request results\n *\n * @param {T[]} results\n * @returns {Map}\n * @template T\n */\n splitResponse(results) {\n const split = new Map();\n for (let i = 0; i < this.requests.length; i++) {\n split.set(this.requests[i], results[i]);\n }\n return split;\n }\n}\n\nexport class ServerData {\n /**\n * @param {any} orm\n * @param {object} params\n * @param {function} params.whenDataIsFetched\n */\n constructor(orm, { whenDataIsFetched }) {\n this.orm = orm;\n this.dataFetchedCallback = whenDataIsFetched;\n /** @type {Record}*/\n this.cache = {};\n /** @type {Record>}*/\n this.asyncCache = {};\n this.batchEndpoints = {};\n }\n\n /**\n * @returns {{get: (resModel:string, method: string, args: unknown) => any}}\n */\n get batch() {\n return { get: (resModel, method, args) => this._getBatchItem(resModel, method, args) };\n }\n\n /**\n * @private\n * @param {string} resModel\n * @param {string} method\n * @param {unknown} args\n * @returns {any}\n */\n _getBatchItem(resModel, method, args) {\n const request = new Request(resModel, method, [args]);\n if (!(request.key in this.cache)) {\n const error = new LoadingDataError();\n this.cache[request.key] = error;\n this._batch(request);\n throw error;\n }\n return this._getOrThrowCachedResponse(request);\n }\n\n /**\n * @param {string} resModel\n * @param {string} method\n * @param {unknown[]} args\n * @returns {any}}\n */\n get(resModel, method, args) {\n const request = new Request(resModel, method, args);\n if (!(request.key in this.cache)) {\n const error = new LoadingDataError();\n this.cache[request.key] = error;\n this.orm\n .call(resModel, method, args)\n .then((result) => (this.cache[request.key] = result))\n .catch((error) => (this.cache[request.key] = error))\n .finally(() => this.dataFetchedCallback());\n throw error;\n }\n return this._getOrThrowCachedResponse(request);\n }\n\n /**\n * Returns the request result if cached or the associated promise\n * @param {string} resModel\n * @param {string} method\n * @param {unknown[]} [args]\n * @returns {Promise}\n */\n async fetch(resModel, method, args) {\n const request = new Request(resModel, method, args);\n if (!(request.key in this.asyncCache)) {\n this.asyncCache[request.key] = this.orm.call(resModel, method, args);\n }\n return this.asyncCache[request.key];\n }\n\n /**\n * @private\n * @param {Request} request\n * @returns {void}\n */\n _batch(request) {\n const endpoint = this._getBatchEndPoint(request.resModel, request.method);\n endpoint.call(request);\n }\n\n /**\n * @private\n * @param {Request} request\n * @return {unknown}\n */\n _getOrThrowCachedResponse(request) {\n const data = this.cache[request.key];\n if (data instanceof Error) {\n throw data;\n }\n return data;\n }\n\n /**\n * @private\n * @param {string} resModel\n * @param {string} method\n */\n _getBatchEndPoint(resModel, method) {\n if (!this.batchEndpoints[resModel] || !this.batchEndpoints[resModel][method]) {\n this.batchEndpoints[resModel] = {\n ...this.batchEndpoints[resModel],\n [method]: this._createBatchEndpoint(resModel, method),\n };\n }\n return this.batchEndpoints[resModel][method];\n }\n\n /**\n * @private\n * @param {string} resModel\n * @param {string} method\n */\n _createBatchEndpoint(resModel, method) {\n return new BatchEndpoint(this.orm, resModel, method, {\n whenDataIsFetched: () => this.dataFetchedCallback(),\n successCallback: (request, result) => (this.cache[request.key] = result),\n failureCallback: (request, error) => (this.cache[request.key] = error),\n });\n }\n}\n\n/**\n * Collect multiple requests into a single batch.\n */\nexport default class BatchEndpoint {\n /**\n * @param {object} orm\n * @param {string} resModel\n * @param {string} method\n * @param {object} callbacks\n * @param {function} callbacks.successCallback\n * @param {function} callbacks.failureCallback\n * @param {function} callbacks.whenDataIsFetched\n */\n constructor(orm, resModel, method, { successCallback, failureCallback, whenDataIsFetched }) {\n this.orm = orm;\n this.resModel = resModel;\n this.method = method;\n this.successCallback = successCallback;\n this.failureCallback = failureCallback;\n this.batchedFetchedCallback = whenDataIsFetched;\n\n this._isScheduled = false;\n this._pendingBatch = new ListRequestBatch(resModel, method);\n }\n\n /**\n * @param {Request} request\n */\n call(request) {\n this._pendingBatch.add(request);\n this._scheduleNextBatch();\n }\n\n /**\n * @param {Map} batchResult\n * @private\n */\n _notifyResults(batchResult) {\n for (const [request, result] of batchResult) {\n if (result instanceof Error) {\n this.failureCallback(request, result);\n } else {\n this.successCallback(request, result);\n }\n }\n }\n\n /**\n * @private\n */\n _scheduleNextBatch() {\n if (this._isScheduled || this._pendingBatch.requests.length === 0) {\n return;\n }\n this._isScheduled = true;\n queueMicrotask(async () => {\n try {\n this._isScheduled = false;\n const batch = this._pendingBatch;\n const { resModel, method } = batch;\n this._pendingBatch = new ListRequestBatch(resModel, method);\n await this.orm\n .call(resModel, method, batch.payload)\n .then((result) => batch.splitResponse(result))\n .catch(() => this._retryOneByOne(batch))\n .then((batchResults) => this._notifyResults(batchResults));\n } finally {\n this.batchedFetchedCallback();\n }\n });\n }\n\n /**\n * @private\n * @param {ListRequestBatch} batch\n * @returns {Promise>}\n */\n async _retryOneByOne(batch) {\n const mergedResults = new Map();\n const { resModel, method } = batch;\n const singleRequestBatches = batch.requests.map(\n (request) => new ListRequestBatch(resModel, method, [request])\n );\n const proms = [];\n for (const batch of singleRequestBatches) {\n const request = batch.requests[0];\n const prom = this.orm\n .call(resModel, method, batch.payload)\n .then((result) =>\n mergedResults.set(request, batch.splitResponse(result).get(request))\n )\n .catch((error) => mergedResults.set(request, error));\n proms.push(prom);\n }\n await Promise.allSettled(proms);\n return mergedResults;\n }\n}\n", "/** @odoo-module */\n\nimport { YearPicker } from \"../year_picker\";\nimport { dateOptions } from \"@spreadsheet/global_filters/helpers\";\n\nconst { DateTime } = luxon;\nconst { Component, onWillUpdateProps } = owl;\n\nexport class DateFilterValue extends Component {\n setup() {\n this._setStateFromProps(this.props);\n onWillUpdateProps(this._setStateFromProps);\n }\n _setStateFromProps(props) {\n this.period = props.period;\n /** @type {number|undefined} */\n this.yearOffset = props.yearOffset;\n // date should be undefined if we don't have the yearOffset\n /** @type {DateTime|undefined} */\n this.date =\n this.yearOffset !== undefined\n ? DateTime.local().plus({ year: this.yearOffset })\n : undefined;\n }\n\n dateOptions(type) {\n return type ? dateOptions(type) : [];\n }\n\n isYear() {\n return this.props.type === \"year\";\n }\n\n isSelected(periodId) {\n return this.period === periodId;\n }\n\n /**\n * @param {Event & { target: HTMLSelectElement }} ev\n */\n onPeriodChanged(ev) {\n this.period = ev.target.value;\n this._updateFilter();\n }\n\n onYearChanged(date) {\n this.date = date;\n this.yearOffset = date.year - DateTime.now().year;\n this._updateFilter();\n }\n\n _updateFilter() {\n this.props.onTimeRangeChanged({\n yearOffset: this.yearOffset || 0,\n period: this.period,\n });\n }\n}\nDateFilterValue.template = \"spreadsheet_edition.DateFilterValue\";\nDateFilterValue.components = { YearPicker };\n\nDateFilterValue.props = {\n // See @spreadsheet_edition/bundle/global_filters/filters_plugin.RangeType\n type: { validate: (/**@type {string} */ t) => [\"year\", \"month\", \"quarter\"].includes(t) },\n onTimeRangeChanged: Function,\n yearOffset: { type: Number, optional: true },\n period: { type: String, optional: true },\n};\n", "/** @odoo-module */\n\nimport { RecordsSelector } from \"../records_selector/records_selector\";\nimport { RELATIVE_DATE_RANGE_TYPES } from \"@spreadsheet/helpers/constants\";\nimport { DateFilterValue } from \"../filter_date_value/filter_date_value\";\n\nconst { Component } = owl;\n\nexport class FilterValue extends Component {\n setup() {\n this.getters = this.props.model.getters;\n this.relativeDateRangesTypes = RELATIVE_DATE_RANGE_TYPES;\n }\n onDateInput(id, value) {\n this.props.model.dispatch(\"SET_GLOBAL_FILTER_VALUE\", { id, value });\n }\n\n onTextInput(id, value) {\n this.props.model.dispatch(\"SET_GLOBAL_FILTER_VALUE\", { id, value });\n }\n\n onTagSelected(id, values) {\n this.props.model.dispatch(\"SET_GLOBAL_FILTER_VALUE\", {\n id,\n value: values.map((record) => record.id),\n displayNames: values.map((record) => record.display_name),\n });\n }\n\n onClear(id) {\n this.props.model.dispatch(\"CLEAR_GLOBAL_FILTER_VALUE\", { id });\n }\n}\nFilterValue.template = \"spreadsheet_edition.FilterValue\";\nFilterValue.components = { RecordsSelector, DateFilterValue };\nFilterValue.props = {\n filter: Object,\n model: Object,\n};\n", "/** @odoo-module **/\n\nimport { Domain } from \"@web/core/domain\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { TagsList } from \"@web/views/fields/many2many_tags/tags_list\";\nimport { Many2XAutocomplete } from \"@web/views/fields/relational_utils\";\n\nconst { Component, onWillStart, onWillUpdateProps } = owl;\n\nexport class RecordsSelector extends Component {\n setup() {\n /** @type {Record} */\n this.displayNames = {};\n /** @type {import(\"@web/core/orm_service\").ORM}*/\n this.orm = useService(\"orm\");\n onWillStart(() => this.fetchMissingDisplayNames(this.props.resModel, this.props.resIds));\n onWillUpdateProps((nextProps) =>\n this.fetchMissingDisplayNames(nextProps.resModel, nextProps.resIds)\n );\n }\n\n get tags() {\n return this.props.resIds.map((id) => ({\n text: this.displayNames[id],\n onDelete: () => this.removeRecord(id),\n displayBadge: true,\n }));\n }\n\n searchDomain() {\n return Domain.not([[\"id\", \"in\", this.props.resIds]]).toList();\n }\n\n /**\n * @param {number} recordId\n */\n removeRecord(recordId) {\n delete this.displayNames[recordId];\n this.notifyChange(this.props.resIds.filter((id) => id !== recordId));\n }\n\n /**\n * @param {{ id: number; name?: string}[]} records\n */\n update(records) {\n for (const record of records.filter((record) => record.name)) {\n this.displayNames[record.id] = record.name;\n }\n this.notifyChange(this.props.resIds.concat(records.map(({ id }) => id)));\n }\n\n /**\n * @param {number[]} selectedIds\n */\n notifyChange(selectedIds) {\n this.props.onValueChanged(\n selectedIds.map((id) => ({ id, display_name: this.displayNames[id] }))\n );\n }\n\n /**\n * @param {string} resModel\n * @param {number[]} recordIds\n */\n async fetchMissingDisplayNames(resModel, recordIds) {\n const missingNameIds = recordIds.filter((id) => !(id in this.displayNames));\n if (missingNameIds.length === 0) {\n return;\n }\n const results = await this.orm.read(resModel, missingNameIds, [\"display_name\"]);\n for (const { id, display_name } of results) {\n this.displayNames[id] = display_name;\n }\n }\n}\nRecordsSelector.components = { TagsList, Many2XAutocomplete };\nRecordsSelector.template = \"spreadsheet.RecordsSelector\";\nRecordsSelector.props = {\n /**\n * Callback called when a record is selected or removed.\n * (selectedRecords: Array<{ id: number; display_name: string }>) => void\n **/\n onValueChanged: Function,\n resModel: String,\n /**\n * Array of selected record ids\n */\n resIds: {\n optional: true,\n type: Array,\n },\n placeholder: {\n optional: true,\n type: String,\n },\n};\nRecordsSelector.defaultProps = {\n resIds: [],\n};\n", "/** @odoo-module */\n\nimport { DatePicker } from \"@web/core/datepicker/datepicker\";\nconst { DateTime } = luxon;\n\nconst DEFAULT_DATE = DateTime.local();\nexport class YearPicker extends DatePicker {\n /**\n * @override\n */\n initFormat() {\n super.initFormat();\n // moment.js format\n this.defaultFormat = \"yyyy\";\n this.staticFormat = \"yyyy\";\n }\n\n /**\n * @override\n */\n getOptions(useStatic = false) {\n return {\n format:\n !useStatic || this.isValidStaticFormat(this.format) ? this.format : this.staticFormat,\n locale: DEFAULT_DATE.locale,\n };\n }\n\n /**\n * @override\n */\n bootstrapDateTimePicker(commandOrParams) {\n if (typeof commandOrParams === \"object\") {\n const widgetParent = window.$(this.rootRef.el);\n commandOrParams = { ...commandOrParams, widgetParent };\n }\n super.bootstrapDateTimePicker(commandOrParams);\n }\n\n /**\n * @override\n */\n onWillUpdateProps(nextProps) {\n const pickerParams = {};\n let shouldUpdateInput = false;\n for (const prop in nextProps) {\n const prev = this.props[prop];\n const next = nextProps[prop];\n if (\n (prev instanceof DateTime && next instanceof DateTime && !prev.equals(next)) ||\n prev !== next\n ) {\n pickerParams[prop] = nextProps[prop];\n if (prop === \"date\") {\n this.setDateAndFormat(nextProps);\n shouldUpdateInput = true;\n }\n }\n }\n if (shouldUpdateInput) {\n this.updateInput();\n }\n this.bootstrapDateTimePicker(pickerParams);\n }\n /**\n * @override: allow displaying empty dates\n */\n updateInput({ useStatic } = {}) {\n const [formattedValue] = this.formatValue(this.date, this.getOptions(useStatic));\n this.inputRef.el.value = formattedValue || this.props.placeholder;\n }\n\n /**\n * @override\n */\n onDateChange({ useStatic } = {}) {\n const [date] = this.parseValue(this.inputRef.el.value, this.getOptions(useStatic));\n if (!date || (this.date instanceof DateTime && date.equals(this.date))) {\n this.updateInput();\n } else {\n this.state.warning = date > DateTime.local();\n this.props.onDateTimeChanged(date);\n }\n }\n}\n\nconst props = {\n ...DatePicker.props,\n date: { type: DateTime, optional: true },\n};\ndelete props[\"format\"];\n\nYearPicker.props = props;\n\nYearPicker.defaultProps = {\n ...DatePicker.defaultProps,\n};\n", "/** @odoo-module */\n\nimport { serializeDate, serializeDateTime } from \"@web/core/l10n/dates\";\nimport { Domain } from \"@web/core/domain\";\n\nimport CommandResult from \"@spreadsheet/o_spreadsheet/cancelled_reason\";\nimport { FILTER_DATE_OPTION, monthsOptions } from \"@spreadsheet/assets_backend/constants\";\nimport { getPeriodOptions } from \"@web/search/utils/dates\";\nimport { RELATIVE_DATE_RANGE_TYPES } from \"@spreadsheet/helpers/constants\";\n\nconst { DateTime } = luxon;\n\n/**\n * @typedef {import(\"@spreadsheet/global_filters/plugins/global_filters_core_plugin\").FieldMatching} FieldMatching\n */\n\nexport function checkFiltersTypeValueCombination(type, value) {\n if (value !== undefined) {\n switch (type) {\n case \"text\":\n if (typeof value !== \"string\") {\n return CommandResult.InvalidValueTypeCombination;\n }\n break;\n case \"date\":\n if (typeof value === \"string\") {\n const expectedValues = RELATIVE_DATE_RANGE_TYPES.map((val) => val.type);\n if (value && !expectedValues.includes(value)) {\n return CommandResult.InvalidValueTypeCombination;\n }\n } else if (typeof value !== \"object\" || Array.isArray(value)) {\n // not a date\n return CommandResult.InvalidValueTypeCombination;\n }\n break;\n case \"relation\":\n if (!Array.isArray(value)) {\n return CommandResult.InvalidValueTypeCombination;\n }\n break;\n }\n }\n return CommandResult.Success;\n}\n\n/**\n *\n * @param {Record} fieldMatchings\n */\nexport function checkFilterFieldMatching(fieldMatchings) {\n for (const fieldMatch of Object.values(fieldMatchings)) {\n if (fieldMatch.offset && (!fieldMatch.chain || !fieldMatch.type)) {\n return CommandResult.InvalidFieldMatch;\n }\n }\n\n return CommandResult.Success;\n}\n\n/**\n * Get a date domain relative to the current date.\n * The domain will span the amount of time specified in rangeType and end the day before the current day.\n *\n *\n * @param {Object} now current time, as luxon time\n * @param {number} offset offset to add to the date\n * @param {\"last_month\" | \"last_week\" | \"last_year\" | \"last_three_years\"} rangeType\n * @param {string} fieldName\n * @param {\"date\" | \"datetime\"} fieldType\n *\n * @returns {Domain|undefined}\n */\nexport function getRelativeDateDomain(now, offset, rangeType, fieldName, fieldType) {\n let endDate = now.minus({ day: 1 }).endOf(\"day\");\n let startDate = endDate;\n switch (rangeType) {\n case \"last_week\": {\n const offsetParam = { day: 7 * offset };\n endDate = endDate.plus(offsetParam);\n startDate = now.minus({ day: 7 }).plus(offsetParam);\n break;\n }\n case \"last_month\": {\n const offsetParam = { day: 30 * offset };\n endDate = endDate.plus(offsetParam);\n startDate = now.minus({ day: 30 }).plus(offsetParam);\n break;\n }\n case \"last_three_months\": {\n const offsetParam = { day: 90 * offset };\n endDate = endDate.plus(offsetParam);\n startDate = now.minus({ day: 90 }).plus(offsetParam);\n break;\n }\n case \"last_six_months\": {\n const offsetParam = { day: 180 * offset };\n endDate = endDate.plus(offsetParam);\n startDate = now.minus({ day: 180 }).plus(offsetParam);\n break;\n }\n case \"last_year\": {\n const offsetParam = { day: 365 * offset };\n endDate = endDate.plus(offsetParam);\n startDate = now.minus({ day: 365 }).plus(offsetParam);\n break;\n }\n case \"last_three_years\": {\n const offsetParam = { day: 3 * 365 * offset };\n endDate = endDate.plus(offsetParam);\n startDate = now.minus({ day: 3 * 365 }).plus(offsetParam);\n break;\n }\n default:\n return undefined;\n }\n startDate = startDate.startOf(\"day\");\n\n let leftBound, rightBound;\n if (fieldType === \"date\") {\n leftBound = serializeDate(startDate);\n rightBound = serializeDate(endDate);\n } else {\n leftBound = serializeDateTime(startDate);\n rightBound = serializeDateTime(endDate);\n }\n\n return new Domain([\"&\", [fieldName, \">=\", leftBound], [fieldName, \"<=\", rightBound]]);\n}\n\n/**\n * Returns a list of time options to choose from according to the requested\n * type. Each option contains its (translated) description.\n * see getPeriodOptions\n *\n *\n * @param {string} type \"month\" | \"quarter\" | \"year\"\n *\n * @returns {Array}\n */\nexport function dateOptions(type) {\n if (type === \"month\") {\n return monthsOptions;\n } else {\n return getPeriodOptions(DateTime.local()).filter(({ id }) =>\n FILTER_DATE_OPTION[type].includes(id)\n );\n }\n}\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nimport GlobalFiltersUIPlugin from \"./plugins/global_filters_ui_plugin\";\nimport { GlobalFiltersCorePlugin } from \"./plugins/global_filters_core_plugin\";\nconst { inverseCommandRegistry } = spreadsheet.registries;\n\nfunction identity(cmd) {\n return [cmd];\n}\n\nconst { coreTypes, invalidateEvaluationCommands, readonlyAllowedCommands } = spreadsheet;\n\ncoreTypes.add(\"ADD_GLOBAL_FILTER\");\ncoreTypes.add(\"EDIT_GLOBAL_FILTER\");\ncoreTypes.add(\"REMOVE_GLOBAL_FILTER\");\n\ninvalidateEvaluationCommands.add(\"ADD_GLOBAL_FILTER\");\ninvalidateEvaluationCommands.add(\"EDIT_GLOBAL_FILTER\");\ninvalidateEvaluationCommands.add(\"REMOVE_GLOBAL_FILTER\");\ninvalidateEvaluationCommands.add(\"SET_GLOBAL_FILTER_VALUE\");\ninvalidateEvaluationCommands.add(\"CLEAR_GLOBAL_FILTER_VALUE\");\n\nreadonlyAllowedCommands.add(\"SET_GLOBAL_FILTER_VALUE\");\nreadonlyAllowedCommands.add(\"SET_MANY_GLOBAL_FILTER_VALUE\");\nreadonlyAllowedCommands.add(\"CLEAR_GLOBAL_FILTER_VALUE\");\nreadonlyAllowedCommands.add(\"UPDATE_OBJECT_DOMAINS\");\n\ninverseCommandRegistry\n .add(\"EDIT_GLOBAL_FILTER\", identity)\n .add(\"ADD_GLOBAL_FILTER\", (cmd) => {\n return [\n {\n type: \"REMOVE_GLOBAL_FILTER\",\n id: cmd.id,\n },\n ];\n })\n .add(\"REMOVE_GLOBAL_FILTER\", (cmd) => {\n return [\n {\n type: \"ADD_GLOBAL_FILTER\",\n id: cmd.id,\n filter: {},\n },\n ];\n });\n\nexport { GlobalFiltersCorePlugin, GlobalFiltersUIPlugin };\n", "/** @odoo-module */\n\n/**\n * @typedef {\"year\"|\"month\"|\"quarter\"|\"relative\"} RangeType\n *\n/**\n * @typedef {Object} FieldMatching\n * @property {string} chain name of the field\n * @property {string} type type of the field\n * @property {number} [offset] offset to apply to the field (for date filters)\n *\n * @typedef {Object} GlobalFilter\n * @property {string} id\n * @property {string} label\n * @property {string} type \"text\" | \"date\" | \"relation\"\n * @property {RangeType} [rangeType]\n * @property {boolean} [defaultsToCurrentPeriod]\n * @property {boolean} [automaticDefaultValue]\n * @property {string|Array|Object} defaultValue Default Value\n * @property {number} [modelID] ID of the related model\n * @property {string} [modelName] Name of the related model\n */\n\nexport const globalFiltersFieldMatchers = {};\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport CommandResult from \"@spreadsheet/o_spreadsheet/cancelled_reason\";\nimport { checkFiltersTypeValueCombination } from \"@spreadsheet/global_filters/helpers\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { escapeRegExp } from \"@web/core/utils/strings\";\n\nexport class GlobalFiltersCorePlugin extends spreadsheet.CorePlugin {\n constructor(config) {\n super(config);\n /** @type {Object.} */\n this.globalFilters = {};\n }\n\n /**\n * Check if the given command can be dispatched\n *\n * @param {Object} cmd Command\n */\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"EDIT_GLOBAL_FILTER\":\n if (!this.getGlobalFilter(cmd.id)) {\n return CommandResult.FilterNotFound;\n } else if (this._isDuplicatedLabel(cmd.id, cmd.filter.label)) {\n return CommandResult.DuplicatedFilterLabel;\n }\n return checkFiltersTypeValueCombination(cmd.filter.type, cmd.filter.defaultValue);\n case \"REMOVE_GLOBAL_FILTER\":\n if (!this.getGlobalFilter(cmd.id)) {\n return CommandResult.FilterNotFound;\n }\n break;\n case \"ADD_GLOBAL_FILTER\":\n if (this._isDuplicatedLabel(cmd.id, cmd.filter.label)) {\n return CommandResult.DuplicatedFilterLabel;\n }\n return checkFiltersTypeValueCombination(cmd.filter.type, cmd.filter.defaultValue);\n }\n return CommandResult.Success;\n }\n\n /**\n * Handle a spreadsheet command\n *\n * @param {Object} cmd Command\n */\n handle(cmd) {\n switch (cmd.type) {\n case \"ADD_GLOBAL_FILTER\":\n this._addGlobalFilter(cmd.filter);\n break;\n case \"EDIT_GLOBAL_FILTER\":\n this._editGlobalFilter(cmd.id, cmd.filter);\n break;\n case \"REMOVE_GLOBAL_FILTER\":\n this._removeGlobalFilter(cmd.id);\n break;\n }\n }\n\n // ---------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------\n\n /**\n * Retrieve the global filter with the given id\n *\n * @param {string} id\n * @returns {GlobalFilter|undefined} Global filter\n */\n getGlobalFilter(id) {\n return this.globalFilters[id];\n }\n\n /**\n * Get the global filter with the given name\n *\n * @param {string} label Label\n *\n * @returns {GlobalFilter|undefined}\n */\n getGlobalFilterLabel(label) {\n return this.getGlobalFilters().find((filter) => _t(filter.label) === _t(label));\n }\n\n /**\n * Retrieve all the global filters\n *\n * @returns {Array} Array of Global filters\n */\n getGlobalFilters() {\n return Object.values(this.globalFilters);\n }\n\n /**\n * Get the default value of a global filter\n *\n * @param {string} id Id of the filter\n *\n * @returns {string|Array|Object}\n */\n getGlobalFilterDefaultValue(id) {\n return this.getGlobalFilter(id).defaultValue;\n }\n\n // ---------------------------------------------------------------------\n // Handlers\n // ---------------------------------------------------------------------\n\n /**\n * Add a global filter\n *\n * @param {GlobalFilter} filter\n */\n _addGlobalFilter(filter) {\n const globalFilters = { ...this.globalFilters };\n globalFilters[filter.id] = filter;\n this.history.update(\"globalFilters\", globalFilters);\n }\n /**\n * Remove a global filter\n *\n * @param {string} id Id of the filter to remove\n */\n _removeGlobalFilter(id) {\n const globalFilters = { ...this.globalFilters };\n delete globalFilters[id];\n this.history.update(\"globalFilters\", globalFilters);\n }\n /**\n * Edit a global filter\n *\n * @param {string} id Id of the filter to update\n * @param {GlobalFilter} newFilter\n */\n _editGlobalFilter(id, newFilter) {\n const currentLabel = this.getGlobalFilter(id).label;\n const globalFilters = { ...this.globalFilters };\n newFilter.id = id;\n globalFilters[id] = newFilter;\n this.history.update(\"globalFilters\", globalFilters);\n const newLabel = this.getGlobalFilter(id).label;\n if (currentLabel !== newLabel) {\n this._updateFilterLabelInFormulas(currentLabel, newLabel);\n }\n }\n\n // ---------------------------------------------------------------------\n // Import/Export\n // ---------------------------------------------------------------------\n\n /**\n * Import the filters\n *\n * @param {Object} data\n */\n import(data) {\n for (const globalFilter of data.globalFilters || []) {\n this.globalFilters[globalFilter.id] = globalFilter;\n }\n }\n /**\n * Export the filters\n *\n * @param {Object} data\n */\n export(data) {\n data.globalFilters = this.getGlobalFilters().map((filter) => ({\n ...filter,\n }));\n }\n\n // ---------------------------------------------------------------------\n // Global filters\n // ---------------------------------------------------------------------\n\n /**\n * Update all ODOO.FILTER.VALUE formulas to reference a filter\n * by its new label.\n *\n * @param {string} currentLabel\n * @param {string} newLabel\n */\n _updateFilterLabelInFormulas(currentLabel, newLabel) {\n const sheetIds = this.getters.getSheetIds();\n currentLabel = escapeRegExp(currentLabel);\n for (const sheetId of sheetIds) {\n for (const cell of Object.values(this.getters.getCells(sheetId))) {\n if (cell.isFormula) {\n const newContent = cell.content.replace(\n new RegExp(`FILTER\\\\.VALUE\\\\(\\\\s*\"${currentLabel}\"\\\\s*\\\\)`, \"g\"),\n `FILTER.VALUE(\"${newLabel}\")`\n );\n if (newContent !== cell.content) {\n const { col, row } = this.getters.getCellPosition(cell.id);\n this.dispatch(\"UPDATE_CELL\", {\n sheetId,\n content: newContent,\n col,\n row,\n });\n }\n }\n }\n }\n }\n\n /**\n * Return true if the label is duplicated\n *\n * @param {string | undefined} filterId\n * @param {string} label\n * @returns {boolean}\n */\n _isDuplicatedLabel(filterId, label) {\n return (\n this.getGlobalFilters().findIndex(\n (filter) => (!filterId || filter.id !== filterId) && filter.label === label\n ) > -1\n );\n }\n}\n\nGlobalFiltersCorePlugin.getters = [\n \"getGlobalFilter\",\n \"getGlobalFilters\",\n \"getGlobalFilterDefaultValue\",\n \"getGlobalFilterLabel\",\n];\n", "/** @odoo-module */\n\n/**\n * @typedef {import(\"@spreadsheet/data_sources/metadata_repository\").Field} Field\n * @typedef {import(\"./global_filters_core_plugin\").GlobalFilter} GlobalFilter\n * @typedef {import(\"./global_filters_core_plugin\").FieldMatching} FieldMatching\n \n */\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport { sprintf } from \"@web/core/utils/strings\";\nimport { Domain } from \"@web/core/domain\";\nimport { constructDateRange, getPeriodOptions, QUARTER_OPTIONS } from \"@web/search/utils/dates\";\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport CommandResult from \"@spreadsheet/o_spreadsheet/cancelled_reason\";\n\nimport { isEmpty } from \"@spreadsheet/helpers/helpers\";\nimport { FILTER_DATE_OPTION } from \"@spreadsheet/assets_backend/constants\";\nimport {\n checkFiltersTypeValueCombination,\n getRelativeDateDomain,\n} from \"@spreadsheet/global_filters/helpers\";\nimport { RELATIVE_DATE_RANGE_TYPES } from \"@spreadsheet/helpers/constants\";\n\nconst { DateTime } = luxon;\n\nconst MONTHS = {\n january: { value: 1, granularity: \"month\" },\n february: { value: 2, granularity: \"month\" },\n march: { value: 3, granularity: \"month\" },\n april: { value: 4, granularity: \"month\" },\n may: { value: 5, granularity: \"month\" },\n june: { value: 6, granularity: \"month\" },\n july: { value: 7, granularity: \"month\" },\n august: { value: 8, granularity: \"month\" },\n september: { value: 9, granularity: \"month\" },\n october: { value: 10, granularity: \"month\" },\n november: { value: 11, granularity: \"month\" },\n december: { value: 12, granularity: \"month\" },\n};\n\nconst { UuidGenerator, createEmptyExcelSheet } = spreadsheet.helpers;\nconst uuidGenerator = new UuidGenerator();\n\nexport default class GlobalFiltersUIPlugin extends spreadsheet.UIPlugin {\n constructor(config) {\n super(config);\n this.orm = config.custom.env ? config.custom.env.services.orm : undefined;\n /**\n * Cache record display names for relation filters.\n * For each filter, contains a promise resolving to\n * the list of display names.\n */\n this.recordsDisplayName = {};\n /** @type {Object.|Object>} */\n this.values = {};\n }\n\n /**\n * Check if the given command can be dispatched\n *\n * @param {Object} cmd Command\n */\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"SET_GLOBAL_FILTER_VALUE\": {\n const filter = this.getters.getGlobalFilter(cmd.id);\n if (!filter) {\n return CommandResult.FilterNotFound;\n }\n return checkFiltersTypeValueCombination(filter.type, cmd.value);\n }\n }\n return CommandResult.Success;\n }\n\n /**\n * Handle a spreadsheet command\n *\n * @param {Object} cmd Command\n */\n handle(cmd) {\n switch (cmd.type) {\n case \"ADD_GLOBAL_FILTER\":\n this.recordsDisplayName[cmd.filter.id] = cmd.filter.defaultValueDisplayNames;\n break;\n case \"EDIT_GLOBAL_FILTER\":\n if (this.values[cmd.id] && this.values[cmd.id].rangeType !== cmd.filter.rangeType) {\n delete this.values[cmd.id];\n }\n this.recordsDisplayName[cmd.filter.id] = cmd.filter.defaultValueDisplayNames;\n break;\n case \"SET_GLOBAL_FILTER_VALUE\":\n this.recordsDisplayName[cmd.id] = cmd.displayNames;\n this._setGlobalFilterValue(cmd.id, cmd.value);\n break;\n case \"SET_MANY_GLOBAL_FILTER_VALUE\":\n for (const filter of cmd.filters) {\n if (filter.value !== undefined) {\n this.dispatch(\"SET_GLOBAL_FILTER_VALUE\", {\n id: filter.filterId,\n value: filter.value,\n });\n } else {\n this.dispatch(\"CLEAR_GLOBAL_FILTER_VALUE\", { id: filter.filterId });\n }\n }\n break;\n case \"REMOVE_GLOBAL_FILTER\":\n delete this.recordsDisplayName[cmd.id];\n delete this.values[cmd.id];\n break;\n case \"CLEAR_GLOBAL_FILTER_VALUE\":\n this.recordsDisplayName[cmd.id] = [];\n this._clearGlobalFilterValue(cmd.id);\n break;\n }\n }\n\n // -------------------------------------------------------------------------\n // Getters\n // -------------------------------------------------------------------------\n\n /**\n * @param {string} filterId\n * @param {FieldMatching} fieldMatching\n *\n * @return {Domain}\n */\n getGlobalFilterDomain(filterId, fieldMatching) {\n /** @type {GlobalFilter} */\n const filter = this.getters.getGlobalFilter(filterId);\n if (!filter) {\n return new Domain();\n }\n switch (filter.type) {\n case \"text\":\n return this._getTextDomain(filter, fieldMatching);\n case \"date\":\n return this._getDateDomain(filter, fieldMatching);\n case \"relation\":\n return this._getRelationDomain(filter, fieldMatching);\n }\n }\n\n /**\n * Get the current value of a global filter\n *\n * @param {string} filterId Id of the filter\n *\n * @returns {string|Array|Object} value Current value to set\n */\n getGlobalFilterValue(filterId) {\n const filter = this.getters.getGlobalFilter(filterId);\n\n const value = filterId in this.values ? this.values[filterId].value : filter.defaultValue;\n\n const preventAutomaticValue =\n this.values[filterId] &&\n this.values[filterId].value &&\n this.values[filterId].value.preventAutomaticValue;\n const defaultsToCurrentPeriod = !preventAutomaticValue && filter.defaultsToCurrentPeriod;\n\n if (filter.type === \"date\" && isEmpty(value) && defaultsToCurrentPeriod) {\n return this._getValueOfCurrentPeriod(filterId);\n }\n\n return value;\n }\n\n /**\n * @param {string} id Id of the filter\n *\n * @returns { boolean } true if the given filter is active\n */\n isGlobalFilterActive(id) {\n const { type } = this.getters.getGlobalFilter(id);\n const value = this.getGlobalFilterValue(id);\n switch (type) {\n case \"text\":\n return value;\n case \"date\":\n return (\n value &&\n (typeof value === \"string\" || value.yearOffset !== undefined || value.period)\n );\n case \"relation\":\n return value && value.length;\n }\n }\n\n /**\n * Get the number of active global filters\n *\n * @returns {number}\n */\n getActiveFilterCount() {\n return this.getters\n .getGlobalFilters()\n .filter((filter) => this.isGlobalFilterActive(filter.id)).length;\n }\n\n getFilterDisplayValue(filterName) {\n const filter = this.getters.getGlobalFilterLabel(filterName);\n if (!filter) {\n throw new Error(sprintf(_t(`Filter \"%s\" not found`), filterName));\n }\n const value = this.getGlobalFilterValue(filter.id);\n switch (filter.type) {\n case \"text\":\n return value || \"\";\n case \"date\": {\n if (value && typeof value === \"string\") {\n const type = RELATIVE_DATE_RANGE_TYPES.find((type) => type.type === value);\n if (!type) {\n return \"\";\n }\n return type.description.toString();\n }\n if (!value || value.yearOffset === undefined) {\n return \"\";\n }\n const periodOptions = getPeriodOptions(DateTime.local());\n const year = String(DateTime.local().year + value.yearOffset);\n const period = periodOptions.find(({ id }) => value.period === id);\n let periodStr = period && period.description;\n // Named months aren't in getPeriodOptions\n if (!period) {\n periodStr =\n MONTHS[value.period] && String(MONTHS[value.period].value).padStart(2, \"0\");\n }\n return periodStr ? periodStr + \"/\" + year : year;\n }\n case \"relation\":\n if (!value || !this.orm) {\n return \"\";\n }\n if (!this.recordsDisplayName[filter.id]) {\n this.orm.call(filter.modelName, \"name_get\", [value]).then((result) => {\n const names = result.map(([, name]) => name);\n this.recordsDisplayName[filter.id] = names;\n this.dispatch(\"EVALUATE_CELLS\", {\n sheetId: this.getters.getActiveSheetId(),\n });\n });\n return \"\";\n }\n return this.recordsDisplayName[filter.id].join(\", \");\n }\n }\n\n // -------------------------------------------------------------------------\n // Handlers\n // -------------------------------------------------------------------------\n\n /**\n * Set the current value of a global filter\n *\n * @param {string} id Id of the filter\n * @param {string|Array|Object} value Current value to set\n */\n _setGlobalFilterValue(id, value) {\n this.values[id] = { value: value, rangeType: this.getters.getGlobalFilter(id).rangeType };\n }\n\n /**\n * Get the filter value corresponding to the current period, depending of the type of range of the filter.\n * For example if rangeType === \"month\", the value will be the current month of the current year.\n *\n * @param {string} filterId a global filter\n * @return {Object} filter value\n */\n _getValueOfCurrentPeriod(filterId) {\n const filter = this.getters.getGlobalFilter(filterId);\n const rangeType = filter.rangeType;\n switch (rangeType) {\n case \"year\":\n return { yearOffset: 0 };\n case \"month\": {\n const month = new Date().getMonth() + 1;\n const period = Object.entries(MONTHS).find((item) => item[1].value === month)[0];\n return { yearOffset: 0, period };\n }\n case \"quarter\": {\n const quarter = Math.floor(new Date().getMonth() / 3);\n const period = FILTER_DATE_OPTION.quarter[quarter];\n return { yearOffset: 0, period };\n }\n }\n return {};\n }\n\n /**\n * Set the current value to empty values which functionally deactivate the filter\n *\n * @param {string} id Id of the filter\n */\n _clearGlobalFilterValue(id) {\n const { type, rangeType } = this.getters.getGlobalFilter(id);\n let value;\n switch (type) {\n case \"text\":\n value = \"\";\n break;\n case \"date\":\n value = { yearOffset: undefined, preventAutomaticValue: true };\n break;\n case \"relation\":\n value = [];\n break;\n }\n this.values[id] = { value, rangeType };\n }\n\n // -------------------------------------------------------------------------\n // Private\n // -------------------------------------------------------------------------\n\n /**\n * Get the domain relative to a date field\n *\n * @private\n *\n * @param {GlobalFilter} filter\n * @param {FieldMatching} fieldMatching\n *\n * @returns {Domain}\n */\n _getDateDomain(filter, fieldMatching) {\n let granularity;\n const value = this.getGlobalFilterValue(filter.id);\n if (!value || !fieldMatching.chain) {\n return new Domain();\n }\n const field = fieldMatching.chain;\n const type = fieldMatching.type;\n const offset = fieldMatching.offset || 0;\n const now = DateTime.local();\n\n if (filter.rangeType === \"relative\") {\n return getRelativeDateDomain(now, offset, value, field, type);\n }\n\n const setParam = { year: now.year };\n const yearOffset = value.yearOffset || 0;\n const plusParam = {\n years: filter.rangeType === \"year\" ? yearOffset + offset : yearOffset,\n };\n if (!value.period || value.period === \"empty\") {\n granularity = \"year\";\n } else {\n switch (filter.rangeType) {\n case \"month\":\n granularity = \"month\";\n setParam.month = MONTHS[value.period].value;\n plusParam.month = offset;\n break;\n case \"quarter\":\n granularity = \"quarter\";\n setParam.quarter = QUARTER_OPTIONS[value.period].setParam.quarter;\n plusParam.quarter = offset;\n break;\n }\n }\n return constructDateRange({\n referenceMoment: now,\n fieldName: field,\n fieldType: type,\n granularity,\n setParam,\n plusParam,\n }).domain;\n }\n\n /**\n * Get the domain relative to a text field\n *\n * @private\n *\n * @param {GlobalFilter} filter\n * @param {FieldMatching} fieldMatching\n *\n * @returns {Domain}\n */\n _getTextDomain(filter, fieldMatching) {\n const value = this.getGlobalFilterValue(filter.id);\n if (!value || !fieldMatching.chain) {\n return new Domain();\n }\n const field = fieldMatching.chain;\n return new Domain([[field, \"ilike\", value]]);\n }\n\n /**\n * Get the domain relative to a relation field\n *\n * @private\n *\n * @param {GlobalFilter} filter\n * @param {FieldMatching} fieldMatching\n *\n * @returns {Domain}\n */\n _getRelationDomain(filter, fieldMatching) {\n const values = this.getGlobalFilterValue(filter.id);\n if (!values || values.length === 0 || !fieldMatching.chain) {\n return new Domain();\n }\n const field = fieldMatching.chain;\n return new Domain([[field, \"in\", values]]);\n }\n\n /**\n * Adds all active filters (and their values) at the time of export in a dedicated sheet\n *\n * @param {Object} data\n */\n exportForExcel(data) {\n if (this.getters.getGlobalFilters().length === 0) {\n return;\n }\n const styles = Object.entries(data.styles);\n let titleStyleId =\n styles.findIndex((el) => JSON.stringify(el[1]) === JSON.stringify({ bold: true })) + 1;\n\n if (titleStyleId <= 0) {\n titleStyleId = styles.length + 1;\n data.styles[styles.length + 1] = { bold: true };\n }\n\n const cells = {};\n cells[\"A1\"] = { content: \"Filter\", style: titleStyleId };\n cells[\"B1\"] = { content: \"Value\", style: titleStyleId };\n let row = 2;\n for (const filter of this.getters.getGlobalFilters()) {\n const content = this.getFilterDisplayValue(filter.label);\n cells[`A${row}`] = { content: filter.label };\n cells[`B${row}`] = { content };\n row++;\n }\n data.sheets.push({\n ...createEmptyExcelSheet(uuidGenerator.uuidv4(), _t(\"Active Filters\")),\n cells,\n colNumber: 2,\n rowNumber: this.getters.getGlobalFilters().length + 1,\n cols: {},\n rows: {},\n merges: [],\n figures: [],\n conditionalFormats: [],\n charts: [],\n });\n }\n}\n\nGlobalFiltersUIPlugin.getters = [\n \"getFilterDisplayValue\",\n \"getGlobalFilterDomain\",\n \"getGlobalFilterValue\",\n \"getActiveFilterCount\",\n \"isGlobalFilterActive\",\n];\n", "/** @odoo-module */\n\nimport { _lt } from \"@web/core/l10n/translation\";\n\nexport const DEFAULT_LINES_NUMBER = 20;\n\nexport const FORMATS = {\n day: { out: \"MM/DD/YYYY\", display: \"DD MMM YYYY\", interval: \"d\" },\n week: { out: \"WW/YYYY\", display: \"[W]W YYYY\", interval: \"w\" },\n month: { out: \"MM/YYYY\", display: \"MMMM YYYY\", interval: \"M\" },\n quarter: { out: \"Q/YYYY\", display: \"[Q]Q YYYY\", interval: \"Q\" },\n year: { out: \"YYYY\", display: \"YYYY\", interval: \"y\" },\n};\n\nexport const HEADER_STYLE = { fillColor: \"#f2f2f2\" };\nexport const TOP_LEVEL_STYLE = { bold: true, fillColor: \"#f2f2f2\" };\nexport const MEASURE_STYLE = { fillColor: \"#f2f2f2\", textColor: \"#756f6f\" };\n\nexport const UNTITLED_SPREADSHEET_NAME = _lt(\"Untitled spreadsheet\");\n\nexport const RELATIVE_DATE_RANGE_TYPES = [\n { type: \"last_week\", description: _lt(\"Last 7 Days\") },\n { type: \"last_month\", description: _lt(\"Last 30 Days\") },\n { type: \"last_three_months\", description: _lt(\"Last 90 Days\") },\n { type: \"last_six_months\", description: _lt(\"Last 180 Days\") },\n { type: \"last_year\", description: _lt(\"Last 365 Days\") },\n { type: \"last_three_years\", description: _lt(\"Last 3 Years\") },\n];\n", "/** @odoo-module */\n\nimport { serializeDate } from \"@web/core/l10n/dates\";\nimport { loadJS } from \"@web/core/assets\";\n\nconst { DateTime } = luxon;\n\n/**\n * Get the intersection of two arrays\n *\n * @param {Array} a\n * @param {Array} b\n *\n * @private\n * @returns {Array} intersection between a and b\n */\nexport function intersect(a, b) {\n return a.filter((x) => b.includes(x));\n}\n\n/**\n * Given an object of form {\"1\": {...}, \"2\": {...}, ...} get the maximum ID used\n * in this object\n * If the object has no keys, return 0\n *\n * @param {Object} o an object for which the keys are an ID\n *\n * @returns {number}\n */\nexport function getMaxObjectId(o) {\n const keys = Object.keys(o);\n if (!keys.length) {\n return 0;\n }\n const nums = keys.map((id) => parseInt(id, 10));\n const max = Math.max(...nums);\n return max;\n}\n\n/** converts and orderBy Object to a string equivalent that can be processed by orm.call */\nexport function orderByToString(orderBy) {\n return orderBy.map((o) => `${o.name} ${o.asc ? \"ASC\" : \"DESC\"}`).join(\", \");\n}\n\n/**\n * Convert a spreadsheet date representation to an odoo\n * server formatted date\n *\n * @param {Date} value\n * @returns {string}\n */\nexport function toServerDateString(value) {\n const date = DateTime.fromJSDate(value);\n return serializeDate(date);\n}\n\n/**\n * @param {number[]} array\n * @returns {number}\n */\nexport function sum(array) {\n return array.reduce((acc, n) => acc + n, 0);\n}\n\nfunction camelToSnakeKey(word) {\n const result = word.replace(/(.){1}([A-Z])/g, \"$1 $2\");\n return result.split(\" \").join(\"_\").toLowerCase();\n}\n\n/**\n * Recursively convert camel case object keys to snake case keys\n * @param {object} obj\n * @returns {object}\n */\nexport function camelToSnakeObject(obj) {\n const result = {};\n for (const [key, value] of Object.entries(obj)) {\n const isPojo = typeof value === \"object\" && value !== null && value.constructor === Object;\n result[camelToSnakeKey(key)] = isPojo ? camelToSnakeObject(value) : value;\n }\n return result;\n}\n\n/**\n * Check if the argument is falsy or is an empty object/array\n *\n * TODO : remove this and replace it by the one in o_spreadsheet xlsx import when its merged\n */\nexport function isEmpty(item) {\n if (!item) {\n return true;\n }\n if (typeof item === \"object\") {\n if (\n Object.values(item).length === 0 ||\n Object.values(item).every((val) => val === undefined)\n ) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Load external libraries required for o-spreadsheet\n * @returns {Promise}\n */\nexport async function loadSpreadsheetDependencies() {\n await loadJS(\"/web/static/lib/Chart/Chart.js\");\n // chartjs-gauge should only be loaded when Chart.js is fully loaded !\n await loadJS(\"/spreadsheet/static/lib/chartjs-gauge/chartjs-gauge.js\");\n}\n", "/** @odoo-module **/\n\nimport spreadsheet from \"../o_spreadsheet/o_spreadsheet_extended\";\n\nconst { parse } = spreadsheet;\n\n/**\n * @typedef {Object} OdooFunctionDescription\n * @property {string} functionName Name of the function\n * @property {Array} args Arguments of the function\n * @property {boolean} isMatched True if the function is matched by the matcher function\n */\n\n/**\n * This function is used to search for the functions which match the given matcher\n * from the given formula\n *\n * @param {string} formula\n * @param {string[]} functionNames e.g. [\"ODOO.LIST\", \"ODOO.LIST.HEADER\"]\n * @private\n * @returns {Array}\n */\nexport function getOdooFunctions(formula, functionNames) {\n const formulaUpperCased = formula.toUpperCase();\n // Parsing is an expensive operation, so we first check if the\n // formula contains one of the function names\n if (!functionNames.some((fn) => formulaUpperCased.includes(fn.toUpperCase()))) {\n return [];\n }\n let ast;\n try {\n ast = parse(formula);\n } catch {\n return [];\n }\n return _getOdooFunctionsFromAST(ast, functionNames);\n}\n\n/**\n * This function is used to search for the functions which match the given matcher\n * from the given AST\n *\n * @param {Object} ast (see o-spreadsheet)\n * @param {string[]} functionNames e.g. [\"ODOO.LIST\", \"ODOO.LIST.HEADER\"]\n *\n * @private\n * @returns {Array}\n */\nfunction _getOdooFunctionsFromAST(ast, functionNames) {\n switch (ast.type) {\n case \"UNARY_OPERATION\":\n return _getOdooFunctionsFromAST(ast.operand, functionNames);\n case \"BIN_OPERATION\": {\n return _getOdooFunctionsFromAST(ast.left, functionNames).concat(\n _getOdooFunctionsFromAST(ast.right, functionNames)\n );\n }\n case \"FUNCALL\": {\n const functionName = ast.value.toUpperCase();\n\n if (functionNames.includes(functionName)) {\n return [{ functionName, args: ast.args, isMatched: true }];\n } else {\n return ast.args.map((arg) => _getOdooFunctionsFromAST(arg, functionNames)).flat();\n }\n }\n default:\n return [];\n }\n}\n", "/** @odoo-module */\n\n/**\n * This file is meant to load the different subparts of the module\n * to guarantee their plugins are loaded in the right order\n *\n * dependency:\n * other plugins\n * |\n * ...\n * |\n * filters\n * /\\ \\\n * / \\ \\\n * pivot list Odoo chart\n */\n\n/** TODO: Introduce a position parameter to the plugin registry in order to load them in a specific order */\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nconst { corePluginRegistry, coreViewsPluginRegistry } = spreadsheet.registries;\n\nimport { GlobalFiltersCorePlugin, GlobalFiltersUIPlugin } from \"@spreadsheet/global_filters/index\";\nimport { PivotCorePlugin, PivotUIPlugin } from \"@spreadsheet/pivot/index\"; // list depends on filter for its getters\nimport { ListCorePlugin, ListUIPlugin } from \"@spreadsheet/list/index\"; // pivot depends on filter for its getters\nimport {\n ChartOdooMenuPlugin,\n OdooChartCorePlugin,\n OdooChartUIPlugin,\n} from \"@spreadsheet/chart/index\"; // Odoochart depends on filter for its getters\n\ncorePluginRegistry.add(\"OdooGlobalFiltersCorePlugin\", GlobalFiltersCorePlugin);\ncorePluginRegistry.add(\"OdooPivotCorePlugin\", PivotCorePlugin);\ncorePluginRegistry.add(\"OdooListCorePlugin\", ListCorePlugin);\ncorePluginRegistry.add(\"odooChartCorePlugin\", OdooChartCorePlugin);\ncorePluginRegistry.add(\"chartOdooMenuPlugin\", ChartOdooMenuPlugin);\n\ncoreViewsPluginRegistry.add(\"OdooGlobalFiltersUIPlugin\", GlobalFiltersUIPlugin);\ncoreViewsPluginRegistry.add(\"OdooPivotUIPlugin\", PivotUIPlugin);\ncoreViewsPluginRegistry.add(\"OdooListUIPlugin\", ListUIPlugin);\ncoreViewsPluginRegistry.add(\"odooChartUIPlugin\", OdooChartUIPlugin);\n", "/** @odoo-module */\n\nimport { registry } from \"@web/core/registry\";\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nimport IrMenuPlugin from \"./ir_ui_menu_plugin\";\n\nimport {\n isMarkdownIrMenuIdUrl,\n isIrMenuXmlUrl,\n isMarkdownViewUrl,\n parseIrMenuXmlUrl,\n parseViewLink,\n parseIrMenuIdLink,\n} from \"./odoo_menu_link_cell\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { sprintf } from \"@web/core/utils/strings\";\n\nconst { urlRegistry, corePluginRegistry } = spreadsheet.registries;\nconst { EvaluationError } = spreadsheet;\n\ncorePluginRegistry.add(\"ir_ui_menu_plugin\", IrMenuPlugin);\n\nclass BadOdooLinkError extends EvaluationError {\n constructor(menuId) {\n super(\n _t(\"#LINK\"),\n sprintf(_t(\"Menu %s not found. You may not have the required access rights.\"), menuId)\n );\n }\n}\n\nexport const spreadsheetLinkMenuCellService = {\n dependencies: [\"menu\"],\n start(env) {\n function _getIrMenuByXmlId(xmlId) {\n const menu = env.services.menu.getAll().find((menu) => menu.xmlid === xmlId);\n if (!menu) {\n throw new BadOdooLinkError(xmlId);\n }\n return menu;\n }\n\n urlRegistry\n .add(\"OdooMenuIdLink\", {\n sequence: 65,\n match: isMarkdownIrMenuIdUrl,\n createLink(url, label) {\n const menuId = parseIrMenuIdLink(url);\n const menu = env.services.menu.getMenu(menuId);\n if (!menu) {\n throw new BadOdooLinkError(menuId);\n }\n return {\n url,\n label,\n isExternal: false,\n isUrlEditable: false,\n };\n },\n urlRepresentation(url) {\n const menuId = parseIrMenuIdLink(url);\n return env.services.menu.getMenu(menuId).name;\n },\n open(url) {\n const menuId = parseIrMenuIdLink(url);\n const menu = env.services.menu.getMenu(menuId);\n env.services.action.doAction(menu.actionID);\n },\n // createCell: (id, content, properties, sheetId, getters) => {\n // const { url } = parseMarkdownLink(content);\n // const menuId = parseIrMenuIdLink(url);\n // const menuName = env.services.menu.getMenu(menuId).name;\n // return new OdooMenuLinkCell(id, content, menuId, menuName, properties);\n // },\n })\n .add(\"OdooMenuXmlLink\", {\n sequence: 66,\n match: isIrMenuXmlUrl,\n createLink(url, label) {\n const xmlId = parseIrMenuXmlUrl(url);\n _getIrMenuByXmlId(xmlId);\n return {\n url,\n label,\n isExternal: false,\n isUrlEditable: false,\n };\n },\n urlRepresentation(url) {\n const xmlId = parseIrMenuXmlUrl(url);\n const menuId = _getIrMenuByXmlId(xmlId).id;\n return env.services.menu.getMenu(menuId).name;\n },\n open(url) {\n const xmlId = parseIrMenuXmlUrl(url);\n const menuId = _getIrMenuByXmlId(xmlId).id;\n const menu = env.services.menu.getMenu(menuId);\n env.services.action.doAction(menu.actionID);\n },\n })\n .add(\"OdooViewLink\", {\n sequence: 67,\n match: isMarkdownViewUrl,\n createLink(url, label) {\n return {\n url,\n label: label,\n isExternal: false,\n isUrlEditable: false,\n };\n },\n urlRepresentation(url) {\n const actionDescription = parseViewLink(url);\n return actionDescription.name;\n },\n open(url) {\n const { viewType, action, name } = parseViewLink(url);\n env.services.action.doAction(\n {\n type: \"ir.actions.act_window\",\n name: name,\n res_model: action.modelName,\n views: action.views,\n target: \"current\",\n domain: action.domain,\n context: action.context,\n },\n { viewType }\n );\n },\n });\n\n return true;\n },\n};\n\nregistry.category(\"services\").add(\"spreadsheetLinkMenuCell\", spreadsheetLinkMenuCellService);\n", "/** @odoo-module */\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nconst { CorePlugin } = spreadsheet;\n\nexport default class IrMenuPlugin extends CorePlugin {\n constructor(config) {\n super(config);\n this.env = config.custom.env;\n }\n\n /**\n * Get an ir menu from an id or an xml id\n * @param {number | string} menuId\n * @returns {object | undefined}\n */\n getIrMenu(menuId) {\n let menu = this.env.services.menu.getMenu(menuId);\n if (!menu) {\n menu = this.env.services.menu.getAll().find((menu) => menu.xmlid === menuId);\n }\n return menu;\n }\n}\nIrMenuPlugin.getters = [\"getIrMenu\"];\n", "/** @odoo-module */\n\nconst VIEW_PREFIX = \"odoo://view/\";\nconst IR_MENU_ID_PREFIX = \"odoo://ir_menu_id/\";\nconst IR_MENU_XML_ID_PREFIX = \"odoo://ir_menu_xml_id/\";\n\n/**\n * @typedef Action\n * @property {Array} domain\n * @property {Object} context\n * @property {string} modelName\n * @property {string} orderBy\n * @property {Array<[boolean, string]>} views\n *\n * @typedef ViewLinkDescription\n * @property {string} name Action name\n * @property {Action} action\n * @property {string} viewType Type of view (list, pivot, ...)\n */\n\n/**\n *\n * @param {string} url\n * @returns {boolean}\n */\nexport function isMarkdownViewUrl(url) {\n return url.startsWith(VIEW_PREFIX);\n}\n\n/**\n *\n * @param {string} viewLink\n * @returns {ViewLinkDescription}\n */\nexport function parseViewLink(viewLink) {\n if (viewLink.startsWith(VIEW_PREFIX)) {\n return JSON.parse(viewLink.substr(VIEW_PREFIX.length));\n }\n throw new Error(`${viewLink} is not a valid view link`);\n}\n\n/**\n * @param {ViewLinkDescription} viewDescription Id of the ir.filter\n * @returns {string}\n */\nexport function buildViewLink(viewDescription) {\n return `${VIEW_PREFIX}${JSON.stringify(viewDescription)}`;\n}\n\n/**\n *\n * @param {string} url\n * @returns {boolean}\n */\nexport function isMarkdownIrMenuIdUrl(url) {\n return url.startsWith(IR_MENU_ID_PREFIX);\n}\n\n/**\n *\n * @param {string} irMenuLink\n * @returns ir.ui.menu record id\n */\nexport function parseIrMenuIdLink(irMenuLink) {\n if (irMenuLink.startsWith(IR_MENU_ID_PREFIX)) {\n return parseInt(irMenuLink.substr(IR_MENU_ID_PREFIX.length), 10);\n }\n throw new Error(`${irMenuLink} is not a valid menu id link`);\n}\n\n/**\n * @param {number} menuId\n * @returns\n */\nexport function buildIrMenuIdLink(menuId) {\n return `${IR_MENU_ID_PREFIX}${menuId}`;\n}\n\n/**\n *\n * @param {string} url\n * @returns {boolean}\n */\nexport function isIrMenuXmlUrl(url) {\n return url.startsWith(IR_MENU_XML_ID_PREFIX);\n}\n\n/**\n *\n * @param {string} irMenuUrl\n * @returns {string} ir.ui.menu record id\n */\nexport function parseIrMenuXmlUrl(irMenuUrl) {\n if (irMenuUrl.startsWith(IR_MENU_XML_ID_PREFIX)) {\n return irMenuUrl.substr(IR_MENU_XML_ID_PREFIX.length);\n }\n throw new Error(`${irMenuUrl} is not a valid menu xml link`);\n}\n/**\n * @param {number} menuXmlId\n * @returns\n */\nexport function buildIrMenuXmlLink(menuXmlId) {\n return `${IR_MENU_XML_ID_PREFIX}${menuXmlId}`;\n}\n", "/** @odoo-module */\nimport { _lt } from \"@web/core/l10n/translation\";\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nimport \"./list_functions\";\n\nimport ListCorePlugin from \"@spreadsheet/list/plugins/list_core_plugin\";\nimport ListUIPlugin from \"@spreadsheet/list/plugins/list_ui_plugin\";\n\nimport { SEE_RECORD_LIST, SEE_RECORD_LIST_VISIBLE } from \"./list_actions\";\nconst { inverseCommandRegistry } = spreadsheet.registries;\n\nfunction identity(cmd) {\n return [cmd];\n}\n\nconst { coreTypes, invalidateEvaluationCommands } = spreadsheet;\nconst { cellMenuRegistry } = spreadsheet.registries;\n\ncoreTypes.add(\"INSERT_ODOO_LIST\");\ncoreTypes.add(\"RENAME_ODOO_LIST\");\ncoreTypes.add(\"REMOVE_ODOO_LIST\");\ncoreTypes.add(\"RE_INSERT_ODOO_LIST\");\ncoreTypes.add(\"UPDATE_ODOO_LIST_DOMAIN\");\ncoreTypes.add(\"ADD_LIST_DOMAIN\");\n\ninvalidateEvaluationCommands.add(\"UPDATE_ODOO_LIST_DOMAIN\");\ninvalidateEvaluationCommands.add(\"REMOVE_ODOO_LIST\");\n\ncellMenuRegistry.add(\"list_see_record\", {\n name: _lt(\"See record\"),\n sequence: 200,\n action: async (env) => {\n const position = env.model.getters.getActivePosition();\n await SEE_RECORD_LIST(position, env);\n },\n isVisible: (env) => {\n const position = env.model.getters.getActivePosition();\n return SEE_RECORD_LIST_VISIBLE(position, env);\n },\n});\n\ninverseCommandRegistry\n .add(\"INSERT_ODOO_LIST\", identity)\n .add(\"UPDATE_ODOO_LIST_DOMAIN\", identity)\n .add(\"RE_INSERT_ODOO_LIST\", identity)\n .add(\"RENAME_ODOO_LIST\", identity)\n .add(\"REMOVE_ODOO_LIST\", identity);\n\nexport { ListCorePlugin, ListUIPlugin };\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { getFirstListFunction, getNumberOfListFormulas } from \"./list_helpers\";\n\nconst { astToFormula } = spreadsheet;\n\nexport const SEE_RECORD_LIST = async (position, env) => {\n const cell = env.model.getters.getCell(position);\n if (!cell) {\n return;\n }\n const { args } = getFirstListFunction(cell.content);\n const evaluatedArgs = args\n .map(astToFormula)\n .map((arg) => env.model.getters.evaluateFormula(arg));\n const listId = env.model.getters.getListIdFromPosition(position);\n const { model } = env.model.getters.getListDefinition(listId);\n const dataSource = await env.model.getters.getAsyncListDataSource(listId);\n const recordId = dataSource.getIdFromPosition(evaluatedArgs[1] - 1);\n if (!recordId) {\n return;\n }\n await env.services.action.doAction({\n type: \"ir.actions.act_window\",\n res_model: model,\n res_id: recordId,\n views: [[false, \"form\"]],\n view_mode: \"form\",\n });\n};\n\nexport const SEE_RECORD_LIST_VISIBLE = (position, env) => {\n const evaluatedCell = env.model.getters.getEvaluatedCell(position);\n const cell = env.model.getters.getCell(position);\n return (\n evaluatedCell.type !== \"empty\" &&\n evaluatedCell.type !== \"error\" &&\n getNumberOfListFormulas(cell.content) === 1 &&\n cell &&\n getFirstListFunction(cell.content).functionName === \"ODOO.LIST\"\n );\n};\n", "/** @odoo-module */\n\nimport { OdooViewsDataSource } from \"@spreadsheet/data_sources/odoo_views_data_source\";\nimport { orderByToString } from \"@spreadsheet/helpers/helpers\";\nimport { LoadingDataError } from \"@spreadsheet/o_spreadsheet/errors\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { sprintf } from \"@web/core/utils/strings\";\n\nimport spreadsheet from \"../o_spreadsheet/o_spreadsheet_extended\";\n\nconst { toNumber } = spreadsheet.helpers;\n\n/**\n * @typedef {import(\"@spreadsheet/data_sources/metadata_repository\").Field} Field\n *\n * @typedef {Object} ListMetaData\n * @property {Array} columns\n * @property {string} resModel\n * @property {Record} fields\n *\n * @typedef {Object} ListSearchParams\n * @property {Array} orderBy\n * @property {Object} domain\n * @property {Object} context\n */\n\nexport default class ListDataSource extends OdooViewsDataSource {\n /**\n * @override\n * @param {Object} services Services (see DataSource)\n * @param {Object} params\n * @param {ListMetaData} params.metaData\n * @param {ListSearchParams} params.searchParams\n * @param {number} params.limit\n */\n constructor(services, params) {\n super(services, params);\n this.maxPosition = params.limit;\n this.maxPositionFetched = 0;\n this.data = [];\n }\n\n /**\n * Increase the max position of the list\n * @param {number} position\n */\n increaseMaxPosition(position) {\n this.maxPosition = Math.max(this.maxPosition, position);\n }\n\n async _load() {\n await super._load();\n if (this.maxPosition === 0) {\n this.data = [];\n return;\n }\n const { domain, orderBy, context } = this._searchParams;\n this.data = await this._orm.searchRead(\n this._metaData.resModel,\n domain,\n this._getFieldsToFetch(),\n {\n order: orderByToString(orderBy),\n limit: this.maxPosition,\n context,\n }\n );\n this.maxPositionFetched = this.maxPosition;\n }\n\n /**\n * Get the fields to fetch from the server.\n * Automatically add the currency field if the field is a monetary field.\n */\n _getFieldsToFetch() {\n const fields = this._metaData.columns.filter((f) => this.getField(f));\n for (const field of fields) {\n if (this.getField(field).type === \"monetary\") {\n fields.push(this.getField(field).currency_field);\n }\n }\n return fields;\n }\n\n /**\n * @param {number} position\n * @returns {number}\n */\n getIdFromPosition(position) {\n this._assertDataIsLoaded();\n const record = this.data[position];\n return record ? record.id : undefined;\n }\n\n /**\n * @param {string} fieldName\n * @returns {string}\n */\n getListHeaderValue(fieldName) {\n this._assertDataIsLoaded();\n const field = this.getField(fieldName);\n return field ? field.string : fieldName;\n }\n\n /**\n * @param {number} position\n * @param {string} fieldName\n * @returns {string|number|undefined}\n */\n getListCellValue(position, fieldName) {\n this._assertDataIsLoaded();\n if (position >= this.maxPositionFetched) {\n this.increaseMaxPosition(position + 1);\n // A reload is needed because the asked position is not already loaded.\n this._triggerFetching();\n throw new LoadingDataError();\n }\n const record = this.data[position];\n if (!record) {\n return \"\";\n }\n const field = this.getField(fieldName);\n if (!field) {\n throw new Error(\n sprintf(\n _t(\"The field %s does not exist or you do not have access to that field\"),\n fieldName\n )\n );\n }\n if (!(fieldName in record)) {\n this._metaData.columns.push(fieldName);\n this._metaData.columns = [...new Set(this._metaData.columns)]; //Remove duplicates\n this._triggerFetching();\n throw new LoadingDataError();\n }\n switch (field.type) {\n case \"many2one\":\n return record[fieldName].length === 2 ? record[fieldName][1] : \"\";\n case \"one2many\":\n case \"many2many\": {\n const labels = record[fieldName]\n .map((id) => this._metadataRepository.getRecordDisplayName(field.relation, id))\n .filter((value) => value !== undefined);\n return labels.join(\", \");\n }\n case \"selection\": {\n const key = record[fieldName];\n const value = field.selection.find((array) => array[0] === key);\n return value ? value[1] : \"\";\n }\n case \"boolean\":\n return record[fieldName] ? \"TRUE\" : \"FALSE\";\n case \"date\":\n case \"datetime\":\n return record[fieldName] ? toNumber(record[fieldName]) : \"\";\n default:\n return record[fieldName] || \"\";\n }\n }\n\n //--------------------------------------------------------------------------\n // Private\n //--------------------------------------------------------------------------\n\n /**\n * Ask the parent data source to force a reload of this data source in the\n * next clock cycle. It's necessary when this.limit was updated and new\n * records have to be fetched.\n */\n _triggerFetching() {\n if (this._fetchingPromise) {\n return;\n }\n this._fetchingPromise = Promise.resolve().then(() => {\n new Promise((resolve) => {\n this.load({ reload: true });\n this._fetchingPromise = undefined;\n resolve();\n });\n });\n }\n}\n", "/** @odoo-module **/\n\nimport { _t } from \"web.core\";\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nconst { args, toString, toNumber } = spreadsheet.helpers;\nconst { functionRegistry } = spreadsheet.registries;\n\n//--------------------------------------------------------------------------\n// Spreadsheet functions\n//--------------------------------------------------------------------------\n\nfunction assertListsExists(listId, getters) {\n if (!getters.isExistingList(listId)) {\n throw new Error(_.str.sprintf(_t('There is no list with id \"%s\"'), listId));\n }\n}\n\nfunctionRegistry.add(\"ODOO.LIST\", {\n description: _t(\"Get the value from a list.\"),\n args: args(`\n list_id (string) ${_t(\"ID of the list.\")}\n index (string) ${_t(\"Position of the record in the list.\")}\n field_name (string) ${_t(\"Name of the field.\")}\n `),\n compute: function (listId, index, fieldName) {\n const id = toString(listId);\n const position = toNumber(index) - 1;\n const field = toString(fieldName);\n assertListsExists(id, this.getters);\n return this.getters.getListCellValue(id, position, field);\n },\n computeFormat: function (listId, index, fieldName) {\n const id = toString(listId.value);\n const position = toNumber(index.value) - 1;\n const field = this.getters.getListDataSource(id).getField(toString(fieldName.value));\n switch (field.type) {\n case \"integer\":\n return \"0\";\n case \"float\":\n return \"#,##0.00\";\n case \"monetary\": {\n const currencyName = this.getters.getListCellValue(\n id,\n position,\n field.currency_field\n );\n return this.getters.getCurrencyFormat(currencyName);\n }\n case \"date\":\n return \"m/d/yyyy\";\n case \"datetime\":\n return \"m/d/yyyy hh:mm:ss\";\n default:\n return undefined;\n }\n },\n returns: [\"NUMBER\", \"STRING\"],\n});\n\nfunctionRegistry.add(\"ODOO.LIST.HEADER\", {\n description: _t(\"Get the header of a list.\"),\n args: args(`\n list_id (string) ${_t(\"ID of the list.\")}\n field_name (string) ${_t(\"Name of the field.\")}\n `),\n compute: function (listId, fieldName) {\n const id = toString(listId);\n const field = toString(fieldName);\n assertListsExists(id, this.getters);\n return this.getters.getListHeaderValue(id, field);\n },\n returns: [\"NUMBER\", \"STRING\"],\n});\n", "/** @odoo-module */\n\nimport { getOdooFunctions } from \"../helpers/odoo_functions_helpers\";\n\n/**\n * Parse a spreadsheet formula and detect the number of LIST functions that are\n * present in the given formula.\n *\n * @param {string} formula\n *\n * @returns {number}\n */\nexport function getNumberOfListFormulas(formula) {\n return getOdooFunctions(formula, [\"ODOO.LIST\", \"ODOO.LIST.HEADER\"]).filter((fn) => fn.isMatched)\n .length;\n}\n\n/**\n * Get the first List function description of the given formula.\n *\n * @param {string} formula\n *\n * @returns {import(\"../helpers/odoo_functions_helpers\").OdooFunctionDescription|undefined}\n */\nexport function getFirstListFunction(formula) {\n return getOdooFunctions(formula, [\"ODOO.LIST\", \"ODOO.LIST.HEADER\"]).find((fn) => fn.isMatched);\n}\n", "/** @odoo-module */\n\nimport spreadsheet from \"../../o_spreadsheet/o_spreadsheet_extended\";\nimport CommandResult from \"../../o_spreadsheet/cancelled_reason\";\nimport { getMaxObjectId } from \"../../helpers/helpers\";\nimport ListDataSource from \"../list_data_source\";\nimport { TOP_LEVEL_STYLE } from \"../../helpers/constants\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { globalFiltersFieldMatchers } from \"@spreadsheet/global_filters/plugins/global_filters_core_plugin\";\nimport { sprintf } from \"@web/core/utils/strings\";\nimport { checkFilterFieldMatching } from \"@spreadsheet/global_filters/helpers\";\nimport { getFirstListFunction, getNumberOfListFormulas } from \"../list_helpers\";\n\n/**\n * @typedef {Object} ListDefinition\n * @property {Array} columns\n * @property {Object} context\n * @property {Array>} domain\n * @property {string} id The id of the list\n * @property {string} model The technical name of the model we are listing\n * @property {string} name Name of the list\n * @property {Array} orderBy\n *\n * @typedef {Object} List\n * @property {string} id\n * @property {string} dataSourceId\n * @property {ListDefinition} definition\n * @property {Object} fieldMatching\n *\n * @typedef {import(\"@spreadsheet/global_filters/plugins/global_filters_core_plugin\").FieldMatching} FieldMatching\n */\n\nconst { CorePlugin } = spreadsheet;\n\nexport default class ListCorePlugin extends CorePlugin {\n constructor(config) {\n super(config);\n this.dataSources = config.custom.dataSources;\n\n this.nextId = 1;\n /** @type {Object.} */\n this.lists = {};\n\n globalFiltersFieldMatchers[\"list\"] = {\n geIds: () => this.getters.getListIds(),\n getDisplayName: (listId) => this.getters.getListName(listId),\n getTag: (listId) => sprintf(_t(\"List #%s\"), listId),\n getFieldMatching: (listId, filterId) => this.getListFieldMatching(listId, filterId),\n waitForReady: () => this.getListsWaitForReady(),\n getModel: (listId) => this.getListDefinition(listId).model,\n getFields: (listId) => this.getListDataSource(listId).getFields(),\n };\n }\n\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"INSERT_ODOO_LIST\":\n if (cmd.id !== this.nextId.toString()) {\n return CommandResult.InvalidNextId;\n }\n if (this.lists[cmd.id]) {\n return CommandResult.ListIdDuplicated;\n }\n break;\n case \"RENAME_ODOO_LIST\":\n if (!(cmd.listId in this.lists)) {\n return CommandResult.ListIdNotFound;\n }\n if (cmd.name === \"\") {\n return CommandResult.EmptyName;\n }\n break;\n case \"ADD_GLOBAL_FILTER\":\n case \"EDIT_GLOBAL_FILTER\":\n if (cmd.list) {\n return checkFilterFieldMatching(cmd.list);\n }\n }\n return CommandResult.Success;\n }\n\n /**\n * Handle a spreadsheet command\n *\n * @param {Object} cmd Command\n */\n handle(cmd) {\n switch (cmd.type) {\n case \"INSERT_ODOO_LIST\": {\n const { sheetId, col, row, id, definition, dataSourceId, linesNumber, columns } =\n cmd;\n const anchor = [col, row];\n this._addList(id, definition, dataSourceId, linesNumber);\n this._insertList(sheetId, anchor, id, linesNumber, columns);\n this.history.update(\"nextId\", parseInt(id, 10) + 1);\n break;\n }\n case \"RE_INSERT_ODOO_LIST\": {\n const { sheetId, col, row, id, linesNumber, columns } = cmd;\n const anchor = [col, row];\n this._insertList(sheetId, anchor, id, linesNumber, columns);\n break;\n }\n case \"RENAME_ODOO_LIST\": {\n this.history.update(\"lists\", cmd.listId, \"definition\", \"name\", cmd.name);\n break;\n }\n case \"REMOVE_ODOO_LIST\": {\n const lists = { ...this.lists };\n delete lists[cmd.listId];\n this.history.update(\"lists\", lists);\n break;\n }\n case \"UPDATE_ODOO_LIST_DOMAIN\": {\n this.history.update(\n \"lists\",\n cmd.listId,\n \"definition\",\n \"searchParams\",\n \"domain\",\n cmd.domain\n );\n const list = this.lists[cmd.listId];\n this.dataSources.add(list.dataSourceId, ListDataSource, list.definition);\n break;\n }\n case \"UNDO\":\n case \"REDO\": {\n const domainEditionCommands = cmd.commands.filter(\n (cmd) => cmd.type === \"UPDATE_ODOO_LIST_DOMAIN\"\n );\n for (const cmd of domainEditionCommands) {\n const list = this.lists[cmd.listId];\n this.dataSources.add(list.dataSourceId, ListDataSource, list.definition);\n }\n break;\n }\n case \"ADD_GLOBAL_FILTER\":\n case \"EDIT_GLOBAL_FILTER\":\n if (cmd.list) {\n this._setListFieldMatching(cmd.filter.id, cmd.list);\n }\n break;\n case \"REMOVE_GLOBAL_FILTER\":\n this._onFilterDeletion(cmd.id);\n break;\n\n case \"START\":\n for (const sheetId of this.getters.getSheetIds()) {\n const cells = this.getters.getCells(sheetId);\n for (const cell of Object.values(cells)) {\n if (cell.isFormula) {\n this._addListPositionToDataSource(cell.content);\n }\n }\n }\n break;\n case \"UPDATE_CELL\":\n if (cmd.content) {\n this._addListPositionToDataSource(cmd.content);\n }\n break;\n }\n }\n\n /**\n * Extract the position of the records asked in the given formula and\n * increase the max position of the corresponding data source.\n *\n * @param {string} content Odoo list formula\n */\n _addListPositionToDataSource(content) {\n if (getNumberOfListFormulas(content) !== 1) {\n return;\n }\n const { functionName, args } = getFirstListFunction(content);\n if (functionName !== \"ODOO.LIST\") {\n return;\n }\n const [listId, positionArg] = args.map((arg) => arg.value.toString());\n if (!(listId in this.lists)) {\n return;\n }\n const position = parseInt(positionArg, 10);\n if (isNaN(position)) {\n return;\n }\n const dataSourceId = this.lists[listId].dataSourceId;\n this.dataSources.get(dataSourceId).increaseMaxPosition(position);\n }\n\n // -------------------------------------------------------------------------\n // Getters\n // -------------------------------------------------------------------------\n\n /**\n * @param {string} id\n * @returns {import(\"@spreadsheet/list/list_data_source\").default|undefined}\n */\n getListDataSource(id) {\n const dataSourceId = this.lists[id].dataSourceId;\n return this.dataSources.get(dataSourceId);\n }\n\n /**\n * @param {string} id\n * @returns {string}\n */\n getListDisplayName(id) {\n return `(#${id}) ${this.getListName(id)}`;\n }\n\n /**\n * @param {string} id\n * @returns {string}\n */\n getListName(id) {\n return _t(this.lists[id].definition.name);\n }\n\n /**\n * @param {string} id\n * @returns {string}\n */\n getListFieldMatch(id) {\n return this.lists[id].fieldMatching;\n }\n\n /**\n * @param {string} id\n * @returns {Promise}\n */\n async getAsyncListDataSource(id) {\n const dataSourceId = this.lists[id].dataSourceId;\n await this.dataSources.load(dataSourceId);\n return this.getListDataSource(id);\n }\n\n /**\n * Retrieve all the list ids\n *\n * @returns {Array} list ids\n */\n getListIds() {\n return Object.keys(this.lists);\n }\n\n /**\n * Retrieve the next available id for a new list\n *\n * @returns {string} id\n */\n getNextListId() {\n return this.nextId.toString();\n }\n\n /**\n * @param {string} id\n * @returns {ListDefinition}\n */\n getListDefinition(id) {\n const def = this.lists[id].definition;\n return {\n columns: [...def.metaData.columns],\n domain: [...def.searchParams.domain],\n model: def.metaData.resModel,\n context: { ...def.searchParams.context },\n orderBy: [...def.searchParams.orderBy],\n id,\n name: def.name,\n };\n }\n\n /**\n * Check if an id is an id of an existing list\n *\n * @param {string} id Id of the list\n *\n * @returns {boolean}\n */\n isExistingList(id) {\n return id in this.lists;\n }\n\n // ---------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------\n\n /**\n *\n * @return {Promise[]}\n */\n getListsWaitForReady() {\n return this.getListIds().map((ListId) => this.getListDataSource(ListId).loadMetadata());\n }\n\n /**\n * Get the current FieldMatching on a list\n *\n * @param {string} listId\n * @param {string} filterId\n */\n getListFieldMatching(listId, filterId) {\n return this.lists[listId].fieldMatching[filterId];\n }\n\n /**\n * Sets the current FieldMatching on a list\n *\n * @param {string} filterId\n * @param {Record} listFieldMatches\n */\n _setListFieldMatching(filterId, listFieldMatches) {\n const lists = { ...this.lists };\n for (const [listId, fieldMatch] of Object.entries(listFieldMatches)) {\n lists[listId].fieldMatching[filterId] = fieldMatch;\n }\n this.history.update(\"lists\", lists);\n }\n\n _onFilterDeletion(filterId) {\n const lists = { ...this.lists };\n for (const listId in lists) {\n this.history.update(\"lists\", listId, \"fieldMatching\", filterId, undefined);\n }\n }\n\n _addList(id, definition, dataSourceId, limit, fieldMatching = {}) {\n const lists = { ...this.lists };\n lists[id] = {\n id,\n definition,\n dataSourceId,\n fieldMatching,\n };\n\n if (!this.dataSources.contains(dataSourceId)) {\n this.dataSources.add(dataSourceId, ListDataSource, {\n ...definition,\n limit,\n });\n }\n this.history.update(\"lists\", lists);\n }\n\n /**\n * Build an Odoo List\n * @param {string} sheetId Id of the sheet\n * @param {[number,number]} anchor Top-left cell in which the list should be inserted\n * @param {string} id Id of the list\n * @param {number} linesNumber Number of records to insert\n * @param {Array} columns Columns ({name, type})\n */\n _insertList(sheetId, anchor, id, linesNumber, columns) {\n this._resizeSheet(sheetId, anchor, columns.length, linesNumber + 1);\n this._insertHeaders(sheetId, anchor, id, columns);\n this._insertValues(sheetId, anchor, id, columns, linesNumber);\n }\n\n _insertHeaders(sheetId, anchor, id, columns) {\n let [col, row] = anchor;\n for (const column of columns) {\n this.dispatch(\"UPDATE_CELL\", {\n sheetId,\n col,\n row,\n content: `=ODOO.LIST.HEADER(${id},\"${column.name}\")`,\n });\n col++;\n }\n this.dispatch(\"SET_FORMATTING\", {\n sheetId,\n style: TOP_LEVEL_STYLE,\n target: [\n {\n top: anchor[1],\n bottom: anchor[1],\n left: anchor[0],\n right: anchor[0] + columns.length - 1,\n },\n ],\n });\n }\n\n _insertValues(sheetId, anchor, id, columns, linesNumber) {\n let col = anchor[0];\n let row = anchor[1] + 1;\n for (let i = 1; i <= linesNumber; i++) {\n col = anchor[0];\n for (const column of columns) {\n this.dispatch(\"UPDATE_CELL\", {\n sheetId,\n col,\n row,\n content: `=ODOO.LIST(${id},${i},\"${column.name}\")`,\n });\n col++;\n }\n row++;\n }\n }\n\n /**\n * Resize the sheet to match the size of the listing. Columns and/or rows\n * could be added to be sure to insert the entire sheet.\n *\n * @param {string} sheetId Id of the sheet\n * @param {[number,number]} anchor Anchor of the list [col,row]\n * @param {number} columns Number of columns of the list\n * @param {number} rows Number of rows of the list\n */\n _resizeSheet(sheetId, anchor, columns, rows) {\n const numberCols = this.getters.getNumberCols(sheetId);\n const deltaCol = numberCols - anchor[0];\n if (deltaCol < columns) {\n this.dispatch(\"ADD_COLUMNS_ROWS\", {\n dimension: \"COL\",\n base: numberCols - 1,\n sheetId: sheetId,\n quantity: columns - deltaCol,\n position: \"after\",\n });\n }\n const numberRows = this.getters.getNumberRows(sheetId);\n const deltaRow = numberRows - anchor[1];\n if (deltaRow < rows) {\n this.dispatch(\"ADD_COLUMNS_ROWS\", {\n dimension: \"ROW\",\n base: numberRows - 1,\n sheetId: sheetId,\n quantity: rows - deltaRow,\n position: \"after\",\n });\n }\n }\n\n // ---------------------------------------------------------------------\n // Import/Export\n // ---------------------------------------------------------------------\n\n /**\n * Import the lists\n *\n * @param {Object} data\n */\n import(data) {\n if (data.lists) {\n for (const [id, list] of Object.entries(data.lists)) {\n const definition = {\n metaData: {\n resModel: list.model,\n columns: list.columns,\n },\n searchParams: {\n domain: list.domain,\n context: list.context,\n orderBy: list.orderBy,\n },\n name: list.name,\n };\n this._addList(id, definition, this.uuidGenerator.uuidv4(), 0, list.fieldMatching);\n }\n }\n this.nextId = data.listNextId || getMaxObjectId(this.lists) + 1;\n }\n /**\n * Export the lists\n *\n * @param {Object} data\n */\n export(data) {\n data.lists = {};\n for (const id in this.lists) {\n data.lists[id] = JSON.parse(JSON.stringify(this.getListDefinition(id)));\n data.lists[id].fieldMatching = this.lists[id].fieldMatching;\n }\n data.listNextId = this.nextId;\n }\n}\n\nListCorePlugin.getters = [\n \"getListDataSource\",\n \"getListDisplayName\",\n \"getAsyncListDataSource\",\n \"getListDefinition\",\n \"getListIds\",\n \"getListName\",\n \"getNextListId\",\n \"isExistingList\",\n \"getListFieldMatch\",\n \"getListFieldMatching\",\n];\n", "/** @odoo-module */\n\nimport spreadsheet from \"../../o_spreadsheet/o_spreadsheet_extended\";\nimport { getFirstListFunction } from \"../list_helpers\";\nimport { Domain } from \"@web/core/domain\";\n\nconst { astToFormula } = spreadsheet;\n\n/**\n * @typedef {import(\"./list_core_plugin\").SpreadsheetList} SpreadsheetList\n */\n\nexport default class ListUIPlugin extends spreadsheet.UIPlugin {\n constructor(config) {\n super(config);\n /** @type {string} */\n this.selectedListId = undefined;\n this.env = config.custom.env;\n }\n\n beforeHandle(cmd) {\n switch (cmd.type) {\n case \"START\":\n // make sure the domains are correctly set before\n // any evaluation\n this._addDomains();\n break;\n }\n }\n\n /**\n * Handle a spreadsheet command\n * @param {Object} cmd Command\n */\n handle(cmd) {\n switch (cmd.type) {\n case \"SELECT_ODOO_LIST\":\n this._selectList(cmd.listId);\n break;\n case \"REFRESH_ODOO_LIST\":\n this._refreshOdooList(cmd.listId);\n break;\n case \"REFRESH_ALL_DATA_SOURCES\":\n this._refreshOdooLists();\n break;\n case \"ADD_GLOBAL_FILTER\":\n case \"EDIT_GLOBAL_FILTER\":\n case \"REMOVE_GLOBAL_FILTER\":\n case \"SET_GLOBAL_FILTER_VALUE\":\n case \"CLEAR_GLOBAL_FILTER_VALUE\":\n this._addDomains();\n break;\n case \"UNDO\":\n case \"REDO\":\n if (\n cmd.commands.find((command) =>\n [\n \"ADD_GLOBAL_FILTER\",\n \"EDIT_GLOBAL_FILTER\",\n \"REMOVE_GLOBAL_FILTER\",\n ].includes(command.type)\n )\n ) {\n this._addDomains();\n }\n break;\n }\n }\n\n // -------------------------------------------------------------------------\n // Handlers\n // -------------------------------------------------------------------------\n\n /**\n * Add an additional domain to a list\n *\n * @private\n *\n * @param {string} listId list id\n *\n */\n _addDomain(listId) {\n const domainList = [];\n for (const [filterId, fieldMatch] of Object.entries(\n this.getters.getListFieldMatch(listId)\n )) {\n domainList.push(this.getters.getGlobalFilterDomain(filterId, fieldMatch));\n }\n const domain = Domain.combine(domainList, \"AND\").toString();\n this.getters.getListDataSource(listId).addDomain(domain);\n }\n\n /**\n * Add an additional domain to all lists\n *\n * @private\n *\n */\n _addDomains() {\n for (const listId of this.getters.getListIds()) {\n this._addDomain(listId);\n }\n }\n\n /**\n * Refresh the cache of a list\n * @param {string} listId Id of the list\n */\n _refreshOdooList(listId) {\n this.getters.getListDataSource(listId).load({ reload: true });\n }\n\n /**\n * Refresh the cache of all the lists\n */\n _refreshOdooLists() {\n for (const listId of this.getters.getListIds()) {\n this._refreshOdooList(listId);\n }\n }\n\n /**\n * Select the given list id. If the id is undefined, it unselect the list.\n * @param {number|undefined} listId Id of the list, or undefined to remove\n * the selected list\n */\n _selectList(listId) {\n this.selectedListId = listId;\n }\n\n // -------------------------------------------------------------------------\n // Getters\n // -------------------------------------------------------------------------\n\n /**\n * Get the computed domain of a list\n *\n * @param {string} listId Id of the list\n * @returns {Array}\n */\n getListComputedDomain(listId) {\n return this.getters.getListDataSource(listId).getComputedDomain();\n }\n\n /**\n * Get the id of the list at the given position. Returns undefined if there\n * is no list at this position\n *\n * @param {{ sheetId: string; col: number; row: number}} position\n *\n * @returns {string|undefined}\n */\n getListIdFromPosition(position) {\n const cell = this.getters.getCell(position);\n if (cell && cell.isFormula) {\n const listFunction = getFirstListFunction(cell.content);\n if (listFunction) {\n const content = astToFormula(listFunction.args[0]);\n return this.getters.evaluateFormula(content).toString();\n }\n }\n return undefined;\n }\n\n /**\n * Get the value of a list header\n *\n * @param {string} listId Id of a list\n * @param {string} fieldName\n */\n getListHeaderValue(listId, fieldName) {\n return this.getters.getListDataSource(listId).getListHeaderValue(fieldName);\n }\n\n /**\n * Get the value for a field of a record in the list\n * @param {string} listId Id of the list\n * @param {number} position Position of the record in the list\n * @param {string} fieldName Field Name\n *\n * @returns {string|undefined}\n */\n getListCellValue(listId, position, fieldName) {\n return this.getters.getListDataSource(listId).getListCellValue(position, fieldName);\n }\n\n /**\n * Get the currently selected list id\n * @returns {number|undefined} Id of the list, undefined if no one is selected\n */\n getSelectedListId() {\n return this.selectedListId;\n }\n}\n\nListUIPlugin.getters = [\n \"getListComputedDomain\",\n \"getListHeaderValue\",\n \"getListIdFromPosition\",\n \"getListCellValue\",\n \"getSelectedListId\",\n];\n", "/** @odoo-module */\n\nexport default {\n Success: 0, // should be imported from o-spreadsheet instead of redefined here\n FilterNotFound: 1000,\n DuplicatedFilterLabel: 1001,\n PivotCacheNotLoaded: 1002,\n InvalidValueTypeCombination: 1003,\n ListIdDuplicated: 1004,\n InvalidNextId: 1005,\n ListIdNotFound: 1006,\n EmptyName: 1007,\n PivotIdNotFound: 1008,\n InvalidFieldMatch: 1009,\n};\n", "/** @odoo-module */\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport spreadsheet from \"./o_spreadsheet_extended\";\n\nconst { EvaluationError, CellErrorLevel } = spreadsheet.helpers;\n\nexport class LoadingDataError extends EvaluationError {\n constructor() {\n super(_t(\"Loading...\"), _t(\"Data is loading\"), CellErrorLevel.silent);\n }\n}\n", "/** @odoo-module */\n\nimport spreadsheet from \"./o_spreadsheet_extended\";\nconst { load, CorePlugin, tokenize, parse, convertAstNodes, astToFormula } = spreadsheet;\nconst { corePluginRegistry } = spreadsheet.registries;\n\nexport const ODOO_VERSION = 5;\n\nconst MAP = {\n PIVOT: \"ODOO.PIVOT\",\n \"PIVOT.HEADER\": \"ODOO.PIVOT.HEADER\",\n \"PIVOT.POSITION\": \"ODOO.PIVOT.POSITION\",\n \"FILTER.VALUE\": \"ODOO.FILTER.VALUE\",\n LIST: \"ODOO.LIST\",\n \"LIST.HEADER\": \"ODOO.LIST.HEADER\",\n};\n\nconst dmyRegex = /^([0|1|2|3][1-9])\\/(0[1-9]|1[0-2])\\/(\\d{4})$/i;\n\nexport function migrate(data) {\n let _data = load(data, !!odoo.debug);\n const version = _data.odooVersion || 0;\n if (version < 1) {\n _data = migrate0to1(_data);\n }\n if (version < 2) {\n _data = migrate1to2(_data);\n }\n if (version < 3) {\n _data = migrate2to3(_data);\n }\n if (version < 4) {\n _data = migrate3to4(_data);\n }\n if (version < 5) {\n _data = migrate4to5(_data);\n }\n return _data;\n}\n\nfunction tokensToString(tokens) {\n return tokens.reduce((acc, token) => acc + token.value, \"\");\n}\n\nfunction migrate0to1(data) {\n for (const sheet of data.sheets) {\n for (const xc in sheet.cells || []) {\n const cell = sheet.cells[xc];\n if (cell.content && cell.content.startsWith(\"=\")) {\n const tokens = tokenize(cell.content);\n for (const token of tokens) {\n if (token.type === \"SYMBOL\" && token.value.toUpperCase() in MAP) {\n token.value = MAP[token.value.toUpperCase()];\n }\n }\n cell.content = tokensToString(tokens);\n }\n }\n }\n return data;\n}\n\nfunction migrate1to2(data) {\n for (const sheet of data.sheets) {\n for (const xc in sheet.cells || []) {\n const cell = sheet.cells[xc];\n if (cell.content && cell.content.startsWith(\"=\")) {\n try {\n cell.content = migratePivotDaysParameters(cell.content);\n } catch {\n continue;\n }\n }\n }\n }\n return data;\n}\n\n/**\n * Migration of global filters\n */\nfunction migrate2to3(data) {\n if (data.globalFilters) {\n for (const gf of data.globalFilters) {\n if (gf.fields) {\n gf.pivotFields = gf.fields;\n delete gf.fields;\n }\n if (\n gf.type === \"date\" &&\n typeof gf.defaultValue === \"object\" &&\n \"year\" in gf.defaultValue\n ) {\n switch (gf.defaultValue.year) {\n case \"last_year\":\n gf.defaultValue.yearOffset = -1;\n break;\n case \"antepenultimate_year\":\n gf.defaultValue.yearOffset = -2;\n break;\n case \"this_year\":\n case undefined:\n gf.defaultValue.yearOffset = 0;\n break;\n }\n delete gf.defaultValue.year;\n }\n if (!gf.listFields) {\n gf.listFields = {};\n }\n if (!gf.graphFields) {\n gf.graphFields = {};\n }\n }\n }\n return data;\n}\n\n/**\n * Migration of list/pivot names\n */\nfunction migrate3to4(data) {\n if (data.lists) {\n for (const list of Object.values(data.lists)) {\n list.name = list.name || list.model;\n }\n }\n if (data.pivots) {\n for (const pivot of Object.values(data.pivots)) {\n pivot.name = pivot.name || pivot.model;\n }\n }\n return data;\n}\n\nfunction migrate4to5(data) {\n for (const filter of data.globalFilters || []) {\n for (const [id, fm] of Object.entries(filter.pivotFields || {})) {\n if (!(data.pivots && id in data.pivots)) {\n delete filter.pivotFields[id];\n continue;\n }\n if (!data.pivots[id].fieldMatching) {\n data.pivots[id].fieldMatching = {};\n }\n data.pivots[id].fieldMatching[filter.id] = { chain: fm.field, type: fm.type };\n if (\"offset\" in fm) {\n data.pivots[id].fieldMatching[filter.id].offset = fm.offset;\n }\n }\n delete filter.pivotFields;\n\n for (const [id, fm] of Object.entries(filter.listFields || {})) {\n if (!(data.lists && id in data.lists)) {\n delete filter.listFields[id];\n continue;\n }\n if (!data.lists[id].fieldMatching) {\n data.lists[id].fieldMatching = {};\n }\n data.lists[id].fieldMatching[filter.id] = { chain: fm.field, type: fm.type };\n if (\"offset\" in fm) {\n data.lists[id].fieldMatching[filter.id].offset = fm.offset;\n }\n }\n delete filter.listFields;\n\n const findFigureFromId = (id) => {\n for (const sheet of data.sheets) {\n const fig = sheet.figures.find((f) => f.id === id);\n if (fig) {\n return fig;\n }\n }\n return undefined;\n };\n for (const [id, fm] of Object.entries(filter.graphFields || {})) {\n const figure = findFigureFromId(id);\n if (!figure) {\n delete filter.graphFields[id];\n continue;\n }\n if (!figure.data.fieldMatching) {\n figure.data.fieldMatching = {};\n }\n figure.data.fieldMatching[filter.id] = { chain: fm.field, type: fm.type };\n if (\"offset\" in fm) {\n figure.data.fieldMatching[filter.id].offset = fm.offset;\n }\n }\n delete filter.graphFields;\n }\n return data;\n}\n\n/**\n * Convert pivot formulas days parameters from day/month/year\n * format to the standard spreadsheet month/day/year format.\n * e.g. =PIVOT.HEADER(1,\"create_date:day\",\"30/07/2022\") becomes =PIVOT.HEADER(1,\"create_date:day\",\"07/30/2022\")\n * @param {string} formulaString\n * @returns {string}\n */\nfunction migratePivotDaysParameters(formulaString) {\n const ast = parse(formulaString);\n const convertedAst = convertAstNodes(ast, \"FUNCALL\", (ast) => {\n if ([\"ODOO.PIVOT\", \"ODOO.PIVOT.HEADER\"].includes(ast.value.toUpperCase())) {\n for (const subAst of ast.args) {\n if (subAst.type === \"STRING\") {\n const date = subAst.value.match(dmyRegex);\n if (date) {\n subAst.value = `${[date[2], date[1], date[3]].join(\"/\")}`;\n }\n }\n }\n }\n return ast;\n });\n return \"=\" + astToFormula(convertedAst);\n}\n\nexport default class OdooVersion extends CorePlugin {\n export(data) {\n data.odooVersion = ODOO_VERSION;\n }\n}\n\nOdooVersion.getters = [];\n\ncorePluginRegistry.add(\"odooMigration\", OdooVersion);\n", "/** @odoo-module */\n\n/**\n * Requires to have the file o_spreadsheet.d.ts included in the file tsconfig.json\n * See https://github.com/odoo/odoo/blob/16.0/addons/web/tooling/types/readme.md\n */\n/** @type {o_spreadsheet} */\nconst spreadsheet = window.o_spreadsheet;\nexport const initCallbackRegistry = new spreadsheet.Registry();\n\nimport { _t } from \"@web/core/l10n/translation\";\nspreadsheet.setTranslationMethod(_t);\n\n// export * from spreadsheet ?\nexport default spreadsheet;\n", "/** @odoo-module **/\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { _t } from \"@web/core/l10n/translation\";\n\nconst { args, toString } = spreadsheet.helpers;\nconst { functionRegistry } = spreadsheet.registries;\n\nfunctionRegistry.add(\"_t\", {\n description: _t(\"Get the translated value of the given string\"),\n args: args(`\n value (string) ${_t(\"Value to translate.\")}\n `),\n compute: function (value) {\n return _t(toString(value));\n },\n returns: [\"STRING\"],\n});\n", "/** @odoo-module */\nimport { _lt } from \"@web/core/l10n/translation\";\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nimport PivotCorePlugin from \"./plugins/pivot_core_plugin\";\nimport PivotUIPlugin from \"./plugins/pivot_ui_plugin\";\n\nimport { SEE_RECORDS_PIVOT, SEE_RECORDS_PIVOT_VISIBLE } from \"./pivot_actions\";\n\nconst { coreTypes, invalidateEvaluationCommands } = spreadsheet;\nconst { cellMenuRegistry } = spreadsheet.registries;\n\nconst { inverseCommandRegistry } = spreadsheet.registries;\n\nfunction identity(cmd) {\n return [cmd];\n}\n\ncoreTypes.add(\"INSERT_PIVOT\");\ncoreTypes.add(\"RENAME_ODOO_PIVOT\");\ncoreTypes.add(\"REMOVE_PIVOT\");\ncoreTypes.add(\"RE_INSERT_PIVOT\");\ncoreTypes.add(\"UPDATE_ODOO_PIVOT_DOMAIN\");\n\ninvalidateEvaluationCommands.add(\"UPDATE_ODOO_PIVOT_DOMAIN\");\ninvalidateEvaluationCommands.add(\"REMOVE_PIVOT\");\ninvalidateEvaluationCommands.add(\"INSERT_PIVOT\");\n\ncellMenuRegistry.add(\"pivot_see_records\", {\n name: _lt(\"See records\"),\n sequence: 175,\n action: async (env) => {\n const position = env.model.getters.getActivePosition();\n await SEE_RECORDS_PIVOT(position, env);\n },\n isVisible: (env) => {\n const position = env.model.getters.getActivePosition();\n return SEE_RECORDS_PIVOT_VISIBLE(position, env);\n },\n});\n\ninverseCommandRegistry\n .add(\"INSERT_PIVOT\", identity)\n .add(\"RENAME_ODOO_PIVOT\", identity)\n .add(\"REMOVE_PIVOT\", identity)\n .add(\"UPDATE_ODOO_PIVOT_DOMAIN\", identity)\n .add(\"RE_INSERT_PIVOT\", identity);\n\nexport { PivotCorePlugin, PivotUIPlugin };\n", "/** @odoo-module */\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { getFirstPivotFunction, getNumberOfPivotFormulas } from \"./pivot_helpers\";\n\nconst { astToFormula } = spreadsheet;\n\nexport const SEE_RECORDS_PIVOT = async (position, env) => {\n const cell = env.model.getters.getCell(position);\n if (!cell) {\n return;\n }\n const { args, functionName } = getFirstPivotFunction(cell.content);\n const evaluatedArgs = args\n .map(astToFormula)\n .map((arg) => env.model.getters.evaluateFormula(arg));\n const pivotId = env.model.getters.getPivotIdFromPosition(position);\n const { model } = env.model.getters.getPivotDefinition(pivotId);\n const dataSource = await env.model.getters.getAsyncPivotDataSource(pivotId);\n const slice = functionName === \"ODOO.PIVOT.HEADER\" ? 1 : 2;\n let argsDomain = evaluatedArgs.slice(slice);\n if (argsDomain[argsDomain.length - 2] === \"measure\") {\n // We have to remove the measure from the domain\n argsDomain = argsDomain.slice(0, argsDomain.length - 2);\n }\n const domain = dataSource.getPivotCellDomain(argsDomain);\n const name = await dataSource.getModelLabel();\n await env.services.action.doAction({\n type: \"ir.actions.act_window\",\n name,\n res_model: model,\n view_mode: \"list\",\n views: [\n [false, \"list\"],\n [false, \"form\"],\n ],\n target: \"current\",\n domain,\n });\n};\n\nexport const SEE_RECORDS_PIVOT_VISIBLE = (position, env) => {\n const evaluatedCell = env.model.getters.getEvaluatedCell(position);\n const cell = env.model.getters.getCell(position);\n return (\n evaluatedCell.type !== \"empty\" &&\n evaluatedCell.type !== \"error\" &&\n cell &&\n getNumberOfPivotFormulas(cell.content) === 1\n );\n};\n", "/** @odoo-module */\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport { OdooViewsDataSource } from \"../data_sources/odoo_views_data_source\";\nimport { SpreadsheetPivotModel } from \"./pivot_model\";\n\nexport default class PivotDataSource extends OdooViewsDataSource {\n /**\n *\n * @override\n * @param {Object} services Services (see DataSource)\n * @param {Object} params\n * @param {import(\"./pivot_model\").PivotMetaData} params.metaData\n * @param {import(\"./pivot_model\").PivotSearchParams} params.searchParams\n */\n constructor(services, params) {\n super(services, params);\n }\n\n async _load() {\n await super._load();\n /** @type {SpreadsheetPivotModel} */\n this._model = new SpreadsheetPivotModel(\n { _t },\n {\n metaData: this._metaData,\n searchParams: this._searchParams,\n },\n {\n orm: this._orm,\n metadataRepository: this._metadataRepository,\n }\n );\n await this._model.load(this._searchParams);\n }\n\n async copyModelWithOriginalDomain() {\n await this.loadMetadata();\n const model = new SpreadsheetPivotModel(\n { _t },\n {\n metaData: this._metaData,\n searchParams: this._initialSearchParams,\n },\n {\n orm: this._orm,\n metadataRepository: this._metadataRepository,\n }\n );\n await model.load(this._initialSearchParams);\n return model;\n }\n\n getReportMeasures() {\n this._assertDataIsLoaded();\n return this._model.getReportMeasures();\n }\n\n /**\n * @param {string[]} domain\n */\n getDisplayedPivotHeaderValue(domain) {\n this._assertDataIsLoaded();\n return this._model.getDisplayedPivotHeaderValue(domain);\n }\n\n /**\n * @param {string[]} domain\n */\n getPivotHeaderValue(domain) {\n this._assertDataIsLoaded();\n return this._model.getPivotHeaderValue(domain);\n }\n\n /**\n * @param {string} measure Field name of the measures\n * @param {string[]} domain\n */\n markAsValueUsed(measure, domain) {\n if (this._model) {\n this._model.markAsValueUsed(measure, domain);\n }\n }\n\n /**\n * @param {string[]} domain\n */\n markAsHeaderUsed(domain) {\n if (this._model) {\n this._model.markAsHeaderUsed(domain);\n }\n }\n\n /**\n * @param {string} measure Field name of the measures\n * @param {string[]} domain\n * @returns {boolean}\n */\n isUsedValue(measure, domain) {\n this._assertDataIsLoaded();\n return this._model.isUsedValue(measure, domain);\n }\n\n /**\n * @param {string[]} domain\n * @returns {boolean}\n */\n isUsedHeader(domain) {\n this._assertDataIsLoaded();\n return this._model.isUsedHeader(domain);\n }\n\n clearUsedValues() {\n if (this._model) {\n this._model.clearUsedValues();\n }\n }\n\n getTableStructure() {\n this._assertDataIsLoaded();\n return this._model.getTableStructure();\n }\n\n /**\n * @param {string} measure Field name of the measures\n * @param {string[]} domain\n */\n getPivotCellValue(measure, domain) {\n this._assertDataIsLoaded();\n return this._model.getPivotCellValue(measure, domain);\n }\n\n /**\n * @param {string[]}\n */\n getPivotCellDomain(domain) {\n this._assertDataIsLoaded();\n return this._model.getPivotCellDomain(domain);\n }\n\n /**\n * @param {string} fieldName\n * @param {string} value raw string value\n * @returns {string}\n */\n getGroupByDisplayLabel(fieldName, value) {\n this._assertDataIsLoaded();\n return this._model.getGroupByDisplayLabel(fieldName, value);\n }\n\n /**\n * @param {string} fieldName\n * @returns {string}\n */\n getFormattedGroupBy(fieldName) {\n this._assertDataIsLoaded();\n return this._model.getFormattedGroupBy(fieldName);\n }\n\n /**\n * @param {string} groupFieldString\n */\n parseGroupField(groupFieldString) {\n this._assertDataIsLoaded();\n return this._model.parseGroupField(groupFieldString);\n }\n\n /**\n * @param {\"COLUMN\" | \"ROW\"} dimension\n * @returns {boolean}\n */\n isGroupedOnlyByOneDate(dimension) {\n this._assertDataIsLoaded();\n return this._model.isGroupedOnlyByOneDate(dimension);\n }\n\n /**\n * @param {\"COLUMN\" | \"ROW\"} dimension\n * @returns {string}\n */\n getGroupOfFirstDate(dimension) {\n this._assertDataIsLoaded();\n return this._model.getGroupOfFirstDate(dimension);\n }\n\n /**\n * @param {\"COLUMN\" | \"ROW\"} dimension\n * @param {number} index\n * @returns {string}\n */\n getGroupByAtIndex(dimension, index) {\n this._assertDataIsLoaded();\n return this._model.getGroupByAtIndex(dimension, index);\n }\n\n /**\n * @param {string} fieldName\n * @returns {boolean}\n */\n isColumnGroupBy(fieldName) {\n this._assertDataIsLoaded();\n return this._model.isColumnGroupBy(fieldName);\n }\n\n /**\n * @param {string} fieldName\n * @returns {boolean}\n */\n isRowGroupBy(fieldName) {\n this._assertDataIsLoaded();\n return this._model.isRowGroupBy(fieldName);\n }\n\n /**\n * @returns {number}\n */\n getNumberOfColGroupBys() {\n this._assertDataIsLoaded();\n return this._model.getNumberOfColGroupBys();\n }\n\n async prepareForTemplateGeneration() {\n this._assertDataIsLoaded();\n await this._model.prepareForTemplateGeneration();\n }\n}\n", "/** @odoo-module **/\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport { sprintf } from \"@web/core/utils/strings\";\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nconst { args, toString } = spreadsheet.helpers;\nconst { functionRegistry } = spreadsheet.registries;\n\n//--------------------------------------------------------------------------\n// Spreadsheet functions\n//--------------------------------------------------------------------------\n\nfunction assertPivotsExists(pivotId, getters) {\n if (!getters.isExistingPivot(pivotId)) {\n throw new Error(sprintf(_t('There is no pivot with id \"%s\"'), pivotId));\n }\n}\n\nfunction assertMeasureExist(pivotId, measure, getters) {\n const { measures } = getters.getPivotDefinition(pivotId);\n if (!measures.includes(measure)) {\n const validMeasures = `(${measures})`;\n throw new Error(\n sprintf(\n _t(\"The argument %s is not a valid measure. Here are the measures: %s\"),\n measure,\n validMeasures\n )\n );\n }\n}\n\nfunction assertDomainLength(domain) {\n if (domain.length % 2 !== 0) {\n throw new Error(_t(\"Function PIVOT takes an even number of arguments.\"));\n }\n}\n\nfunctionRegistry\n .add(\"ODOO.FILTER.VALUE\", {\n description: _t(\"Return the current value of a spreadsheet filter.\"),\n args: args(`\n filter_name (string) ${_t(\"The label of the filter whose value to return.\")}\n `),\n compute: function (filterName) {\n return this.getters.getFilterDisplayValue(filterName);\n },\n returns: [\"STRING\"],\n })\n .add(\"ODOO.PIVOT\", {\n description: _t(\"Get the value from a pivot.\"),\n args: args(`\n pivot_id (string) ${_t(\"ID of the pivot.\")}\n measure_name (string) ${_t(\"Name of the measure.\")}\n domain_field_name (string,optional,repeating) ${_t(\"Field name.\")}\n domain_value (string,optional,repeating) ${_t(\"Value.\")}\n `),\n compute: function (pivotId, measureName, ...domain) {\n pivotId = toString(pivotId);\n const measure = toString(measureName);\n const args = domain.map(toString);\n assertPivotsExists(pivotId, this.getters);\n assertMeasureExist(pivotId, measure, this.getters);\n assertDomainLength(args);\n return this.getters.getPivotCellValue(pivotId, measure, args);\n },\n computeFormat: function (pivotId, measureName, ...domain) {\n pivotId = toString(pivotId.value);\n const measure = toString(measureName.value);\n const field = this.getters.getPivotDataSource(pivotId).getReportMeasures()[measure];\n if (!field) {\n return undefined;\n }\n switch (field.type) {\n case \"integer\":\n return \"0\";\n case \"float\":\n return \"#,##0.00\";\n case \"monetary\":\n return this.getters.getCompanyCurrencyFormat() || \"#,##0.00\";\n default:\n return undefined;\n }\n },\n returns: [\"NUMBER\", \"STRING\"],\n })\n .add(\"ODOO.PIVOT.HEADER\", {\n description: _t(\"Get the header of a pivot.\"),\n args: args(`\n pivot_id (string) ${_t(\"ID of the pivot.\")}\n domain_field_name (string,optional,repeating) ${_t(\"Field name.\")}\n domain_value (string,optional,repeating) ${_t(\"Value.\")}\n `),\n compute: function (pivotId, ...domain) {\n pivotId = toString(pivotId);\n const args = domain.map(toString);\n assertPivotsExists(pivotId, this.getters);\n assertDomainLength(args);\n return this.getters.getDisplayedPivotHeaderValue(pivotId, args);\n },\n computeFormat: function (pivotId, ...domain) {\n pivotId = toString(pivotId.value);\n const pivot = this.getters.getPivotDataSource(pivotId);\n const len = domain.length;\n if (!len) {\n return undefined;\n }\n const fieldName = toString(domain[len - 2].value);\n const value = toString(domain[len - 1].value);\n if (fieldName === \"measure\" || value === \"false\") {\n return undefined;\n }\n const { aggregateOperator, field } = pivot.parseGroupField(fieldName);\n switch (field.type) {\n case \"integer\":\n return \"0\";\n case \"float\":\n case \"monetary\":\n return \"#,##0.00\";\n case \"date\":\n case \"datetime\":\n if (aggregateOperator === \"day\") {\n return \"mm/dd/yyyy\";\n }\n return undefined;\n default:\n return undefined;\n }\n },\n returns: [\"NUMBER\", \"STRING\"],\n })\n .add(\"ODOO.PIVOT.POSITION\", {\n description: _t(\"Get the absolute ID of an element in the pivot\"),\n args: args(`\n pivot_id (string) ${_t(\"ID of the pivot.\")}\n field_name (string) ${_t(\"Name of the field.\")}\n position (number) ${_t(\"Position in the pivot\")}\n `),\n compute: function () {\n throw new Error(_t(`[[FUNCTION_NAME]] cannot be called from the spreadsheet.`));\n },\n returns: [\"STRING\"],\n });\n", "/** @odoo-module **/\n\nimport { _t } from \"web.core\";\nimport { FORMATS } from \"../helpers/constants\";\nimport { getOdooFunctions } from \"../helpers/odoo_functions_helpers\";\n\nexport const pivotFormulaRegex = /^=.*PIVOT/;\n\n//--------------------------------------------------------------------------\n// Public\n//--------------------------------------------------------------------------\n\n/**\n * Format a data\n *\n * @param {string} interval aggregate interval i.e. month, week, quarter, ...\n * @param {string} value\n */\nexport function formatDate(interval, value) {\n const output = FORMATS[interval].display;\n const input = FORMATS[interval].out;\n const date = moment(value, input);\n return date.isValid() ? date.format(output) : _t(\"None\");\n}\n\n/**\n * Parse a spreadsheet formula and detect the number of PIVOT functions that are\n * present in the given formula.\n *\n * @param {string} formula\n *\n * @returns {number}\n */\nexport function getNumberOfPivotFormulas(formula) {\n return getOdooFunctions(formula, [\n \"ODOO.PIVOT\",\n \"ODOO.PIVOT.HEADER\",\n \"ODOO.PIVOT.POSITION\",\n ]).filter((fn) => fn.isMatched).length;\n}\n\n/**\n * Get the first Pivot function description of the given formula.\n *\n * @param {string} formula\n *\n * @returns {import(\"../helpers/odoo_functions_helpers\").OdooFunctionDescription|undefined}\n */\nexport function getFirstPivotFunction(formula) {\n return getOdooFunctions(formula, [\n \"ODOO.PIVOT\",\n \"ODOO.PIVOT.HEADER\",\n \"ODOO.PIVOT.POSITION\",\n ]).find((fn) => fn.isMatched);\n}\n\n/**\n * Build a pivot formula expression\n *\n * @param {string} formula formula to be used (PIVOT or PIVOT.HEADER)\n * @param {*} args arguments of the formula\n *\n * @returns {string}\n */\nexport function makePivotFormula(formula, args) {\n return `=${formula}(${args\n .map((arg) =>\n typeof arg == \"number\" || (typeof arg == \"string\" && !isNaN(arg))\n ? `${arg}`\n : `\"${arg.toString().replace(/\"/g, '\\\\\"')}\"`\n )\n .join(\",\")})`;\n}\n\nexport const PERIODS = {\n day: _t(\"Day\"),\n week: _t(\"Week\"),\n month: _t(\"Month\"),\n quarter: _t(\"Quarter\"),\n year: _t(\"Year\"),\n};\n", "/** @odoo-module */\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport { Domain } from \"@web/core/domain\";\nimport { sprintf } from \"@web/core/utils/strings\";\nimport { PivotModel } from \"@web/views/pivot/pivot_model\";\nimport { computeReportMeasures } from \"@web/views/utils\";\nimport { session } from \"@web/session\";\n\nimport { FORMATS } from \"../helpers/constants\";\n\nimport spreadsheet from \"../o_spreadsheet/o_spreadsheet_extended\";\nimport { formatDate } from \"./pivot_helpers\";\nimport { PERIODS } from \"@spreadsheet/pivot/pivot_helpers\";\nimport { SpreadsheetPivotTable } from \"@spreadsheet/pivot/pivot_table\";\n\nconst { toString, toNumber, toBoolean } = spreadsheet.helpers;\n\n/**\n * @typedef {import(\"@spreadsheet/data_sources/metadata_repository\").Field} Field\n * @typedef {import(\"@spreadsheet/pivot/pivot_table\").Row} Row\n * @typedef {import(\"@spreadsheet/pivot/pivot_table\").Column} Column\n *\n * @typedef {Object} PivotMetaData\n * @property {Array} colGroupBys\n * @property {Array} rowGroupBys\n * @property {Array} activeMeasures\n * @property {string} resModel\n * @property {Record} fields\n * @property {string|undefined} modelLabel\n *\n * @typedef {Object} PivotSearchParams\n * @property {Array} groupBy\n * @property {Array} orderBy\n * @property {Object} domain\n * @property {Object} context\n */\n\n/**\n * Parses the positional char (#), the field and operator string of pivot group.\n * e.g. \"create_date:month\"\n * @param {Record} allFields\n * @param {string} groupFieldString\n * @returns {{field: Field, aggregateOperator: string, isPositional: boolean}}\n */\nfunction parseGroupField(allFields, groupFieldString) {\n let [fieldName, aggregateOperator] = groupFieldString.split(\":\");\n const isPositional = fieldName.startsWith(\"#\");\n fieldName = isPositional ? fieldName.substring(1) : fieldName;\n const field = allFields[fieldName];\n if (field === undefined) {\n throw new Error(sprintf(_t(\"Field %s does not exist\"), fieldName));\n }\n if ([\"date\", \"datetime\"].includes(field.type)) {\n aggregateOperator = aggregateOperator || \"month\";\n }\n return {\n isPositional,\n field,\n aggregateOperator,\n };\n}\n\nconst UNSUPPORTED_FIELD_TYPES = [\"one2many\", \"binary\", \"html\"];\nexport const NO_RECORD_AT_THIS_POSITION = Symbol(\"NO_RECORD_AT_THIS_POSITION\");\n\nfunction isNotSupported(fieldType) {\n return UNSUPPORTED_FIELD_TYPES.includes(fieldType);\n}\n\nfunction throwUnsupportedFieldError(field) {\n throw new Error(\n sprintf(_t(\"Field %s is not supported because of its type (%s)\"), field.string, field.type)\n );\n}\n\n/**\n * Parses the value defining a pivot group in a PIVOT formula\n * e.g. given the following formula PIVOT(\"1\", \"stage_id\", \"42\", \"status\", \"won\"),\n * the two group values are \"42\" and \"won\".\n * @param {object} field\n * @param {number | boolean | string} groupValue\n * @returns {number | boolean | string}\n */\nexport function parsePivotFormulaFieldValue(field, groupValue) {\n const groupValueString =\n typeof groupValue === \"boolean\"\n ? toString(groupValue).toLocaleLowerCase()\n : toString(groupValue);\n if (isNotSupported(field.type)) {\n throwUnsupportedFieldError(field);\n }\n // represents a field which is not set (=False server side)\n if (groupValueString === \"false\") {\n return false;\n }\n switch (field.type) {\n case \"datetime\":\n case \"date\":\n return toString(groupValueString);\n case \"selection\":\n case \"char\":\n case \"text\":\n return toString(groupValueString);\n case \"boolean\":\n return toBoolean(groupValueString);\n case \"float\":\n case \"integer\":\n case \"monetary\":\n case \"many2one\":\n case \"many2many\":\n return toNumber(groupValueString);\n default:\n throwUnsupportedFieldError(field);\n }\n}\n\n/**\n * This class is an extension of PivotModel with some additional information\n * that we need in spreadsheet (name_get, isUsedInSheet, ...)\n */\nexport class SpreadsheetPivotModel extends PivotModel {\n /**\n * @param {Object} params\n * @param {PivotMetaData} params.metaData\n * @param {PivotSearchParams} params.searchParams\n * @param {Object} services\n * @param {import(\"../data_sources/metadata_repository\").MetadataRepository} services.metadataRepository\n */\n setup(params, services) {\n // fieldAttrs is required, but not needed in Spreadsheet, so we define it as empty\n (params.metaData.fieldAttrs = {}), super.setup(params);\n\n this.metadataRepository = services.metadataRepository;\n\n /**\n * Contains the domain of the values used during the evaluation of the formula =Pivot(...)\n * Is used to know if a pivot cell is missing or not\n * */\n\n this._usedValueDomains = new Set();\n /**\n * Contains the domain of the headers used during the evaluation of the formula =Pivot.header(...)\n * Is used to know if a pivot cell is missing or not\n * */\n this._usedHeaderDomains = new Set();\n\n /**\n * Display name of the model\n */\n this._modelLabel = params.metaData.modelLabel;\n }\n\n //--------------------------------------------------------------------------\n // Metadata getters\n //--------------------------------------------------------------------------\n\n /**\n * Return true if the given field name is part of the col group bys\n * @param {string} fieldName\n * @returns {boolean}\n */\n isColumnGroupBy(fieldName) {\n try {\n const { field } = this.parseGroupField(fieldName);\n return this._isCol(field);\n } catch {\n return false;\n }\n }\n\n /**\n * Return true if the given field name is part of the row group bys\n * @param {string} fieldName\n * @returns {boolean}\n */\n isRowGroupBy(fieldName) {\n try {\n const { field } = this.parseGroupField(fieldName);\n return this._isRow(field);\n } catch {\n return false;\n }\n }\n\n /**\n * Get the display name of a group by\n * @param {string} fieldName\n * @returns {string}\n */\n getFormattedGroupBy(fieldName) {\n const { field, aggregateOperator } = this.parseGroupField(fieldName);\n return field.string + (aggregateOperator ? ` (${PERIODS[aggregateOperator]})` : \"\");\n }\n\n getReportMeasures() {\n return computeReportMeasures(this.metaData.fields, this.metaData.fieldAttrs, []);\n }\n\n //--------------------------------------------------------------------------\n // Cell missing\n //--------------------------------------------------------------------------\n\n /**\n * Reset the used values and headers\n */\n clearUsedValues() {\n this._usedHeaderDomains.clear();\n this._usedValueDomains.clear();\n }\n\n /**\n * Check if the given domain with the given measure has been used\n */\n isUsedValue(domain, measure) {\n const tag = [measure, ...domain];\n return this._usedValueDomains.has(tag.join());\n }\n\n /**\n * Check if the given domain has been used\n */\n isUsedHeader(domain) {\n return this._usedHeaderDomains.has(domain.join());\n }\n\n /**\n * Indicate that the given domain has been used with the given measure\n */\n markAsValueUsed(domain, measure) {\n const toTag = [measure, ...domain];\n this._usedValueDomains.add(toTag.join());\n }\n\n /**\n * Indicate that the given domain has been used\n */\n markAsHeaderUsed(domain) {\n this._usedHeaderDomains.add(domain.join());\n }\n\n //--------------------------------------------------------------------------\n // Autofill\n //--------------------------------------------------------------------------\n\n /**\n * @param {string} dimension COLUMN | ROW\n */\n isGroupedOnlyByOneDate(dimension) {\n const groupBys =\n dimension === \"COLUMN\" ? this.metaData.fullColGroupBys : this.metaData.fullRowGroupBys;\n return groupBys.length === 1 && this._isDateField(this.parseGroupField(groupBys[0]).field);\n }\n /**\n * @param {string} dimension COLUMN | ROW\n */\n getGroupOfFirstDate(dimension) {\n if (!this.isGroupedOnlyByOneDate(dimension)) {\n return undefined;\n }\n const groupBys =\n dimension === \"COLUMN\" ? this.metaData.fullColGroupBys : this.metaData.fullRowGroupBys;\n return this.parseGroupField(groupBys[0]).aggregateOperator;\n }\n\n /**\n * @param {string} dimension COLUMN | ROW\n * @param {number} index\n */\n getGroupByAtIndex(dimension, index) {\n const groupBys =\n dimension === \"COLUMN\" ? this.metaData.fullColGroupBys : this.metaData.fullRowGroupBys;\n return groupBys[index];\n }\n\n getNumberOfColGroupBys() {\n return this.metaData.fullColGroupBys.length;\n }\n\n //--------------------------------------------------------------------------\n // Evaluation\n //--------------------------------------------------------------------------\n\n /**\n * Get the value of the given domain for the given measure\n */\n getPivotCellValue(measure, domain) {\n const { cols, rows } = this._getColsRowsValuesFromDomain(domain);\n const group = JSON.stringify([rows, cols]);\n const values = this.data.measurements[group];\n return (values && values[0][measure]) || \"\";\n }\n\n /**\n * Get the label the given field-value\n *\n * @param {string} groupFieldString Name of the field\n * @param {string} groupValueString Value of the group by\n * @returns {string}\n */\n getGroupByDisplayLabel(groupFieldString, groupValueString) {\n if (groupValueString === NO_RECORD_AT_THIS_POSITION) {\n return \"\";\n }\n if (groupFieldString === \"measure\") {\n if (groupValueString === \"__count\") {\n return _t(\"Count\");\n }\n // the value is actually the measure field name\n return this.parseGroupField(groupValueString).field.string;\n }\n const { field, aggregateOperator } = this.parseGroupField(groupFieldString);\n const value = parsePivotFormulaFieldValue(field, groupValueString);\n const undef = _t(\"None\");\n if (this._isDateField(field)) {\n if (value && aggregateOperator === \"day\") {\n return toNumber(value);\n }\n return formatDate(aggregateOperator, value);\n }\n if (field.relation) {\n const label = this.metadataRepository.getRecordDisplayName(field.relation, value);\n if (!label) {\n return undef;\n }\n return label;\n }\n const label = this.metadataRepository.getLabel(this.metaData.resModel, field.name, value);\n if (!label) {\n return undef;\n }\n return label;\n }\n\n /**\n * Get the label of the last group by of the domain\n *\n * @param {string[]} domain Domain of the formula\n */\n getPivotHeaderValue(domain) {\n const groupFieldString = domain[domain.length - 2];\n if (groupFieldString.startsWith(\"#\")) {\n const { field } = this.parseGroupField(groupFieldString);\n const { cols, rows } = this._getColsRowsValuesFromDomain(domain);\n return this._isCol(field) ? cols[cols.length - 1] : rows[rows.length - 1];\n }\n const groupValueString = domain[domain.length - 1];\n return groupValueString;\n }\n\n /**\n * Get the displayed label of the last group by of the domain\n *\n * @param {string[]} domain Domain of the formula\n * @returns {string}\n */\n getDisplayedPivotHeaderValue(domain) {\n const groupFieldString = domain[domain.length - 2];\n return this.getGroupByDisplayLabel(groupFieldString, this.getPivotHeaderValue(domain));\n }\n\n //--------------------------------------------------------------------------\n // Misc\n //--------------------------------------------------------------------------\n\n /**\n * Get the Odoo domain corresponding to the given domain\n */\n getPivotCellDomain(domain) {\n const { cols, rows } = this._getColsRowsValuesFromDomain(domain);\n const key = JSON.stringify([rows, cols]);\n const domains = this.data.groupDomains[key];\n return domains ? domains[0] : Domain.FALSE.toList();\n }\n\n /**\n * @returns {SpreadsheetPivotTable}\n */\n getTableStructure() {\n const cols = this._getSpreadsheetCols();\n const rows = this._getSpreadsheetRows(this.data.rowGroupTree);\n rows.push(rows.shift()); //Put the Total row at the end.\n const measures = this.metaData.activeMeasures;\n return new SpreadsheetPivotTable(cols, rows, measures);\n }\n\n //--------------------------------------------------------------------------\n // Private\n //--------------------------------------------------------------------------\n\n /**\n * @override\n */\n async _loadData(config) {\n /** @type {(groupFieldString: string) => ReturnType} */\n this.parseGroupField = parseGroupField.bind(null, this.metaData.fields);\n /*\n * prune is manually set to false in order to expand all the groups\n * automatically\n */\n const prune = false;\n await super._loadData(config, prune);\n\n const metadataRepository = this.metadataRepository;\n\n const registerLabels = (tree, groupBys) => {\n const group = tree.root;\n if (!tree.directSubTrees.size) {\n for (let i = 0; i < group.values.length; i++) {\n const { field } = this.parseGroupField(groupBys[i]);\n if (!field.relation) {\n metadataRepository.registerLabel(\n config.metaData.resModel,\n field.name,\n group.values[i],\n group.labels[i]\n );\n } else {\n metadataRepository.setDisplayName(\n field.relation,\n group.values[i],\n group.labels[i]\n );\n }\n }\n }\n [...tree.directSubTrees.values()].forEach((subTree) => {\n registerLabels(subTree, groupBys);\n });\n };\n\n registerLabels(this.data.colGroupTree, this.metaData.fullColGroupBys);\n registerLabels(this.data.rowGroupTree, this.metaData.fullRowGroupBys);\n }\n\n /**\n * Determines if the given field is a date or datetime field.\n *\n * @param {Field} field Field description\n * @private\n * @returns {boolean} True if the type of the field is date or datetime\n */\n _isDateField(field) {\n return [\"date\", \"datetime\"].includes(field.type);\n }\n\n /**\n * @override\n */\n _getGroupValues(group, groupBys) {\n return groupBys.map((groupBy) => {\n const { field, aggregateOperator } = this.parseGroupField(groupBy);\n if (this._isDateField(field)) {\n const value = this._getGroupStartingDay(groupBy, group);\n if (!value) {\n return false;\n }\n const fOut = FORMATS[aggregateOperator][\"out\"];\n // eslint-disable-next-line no-undef\n const date = moment(value);\n return date.isValid() ? date.format(fOut) : false;\n }\n return this._sanitizeValue(group[groupBy]);\n });\n }\n\n /**\n * When grouping by a time field, return\n * the group starting day (local to the timezone)\n * @param {string} groupBy\n * @param {object} readGroup\n * @returns {string | undefined}\n */\n _getGroupStartingDay(groupBy, readGroup) {\n if (!readGroup[\"__range\"] || !readGroup[\"__range\"][groupBy]) {\n return undefined;\n }\n const { field } = this.parseGroupField(groupBy);\n const sqlValue = readGroup[\"__range\"][groupBy].from;\n if (this.metaData.fields[field.name].type === \"date\") {\n return sqlValue;\n }\n const userTz = session.user_context.tz || luxon.Settings.defaultZoneName;\n return luxon.DateTime.fromSQL(sqlValue, { zone: \"utc\" }).setZone(userTz).toISODate();\n }\n\n /**\n * Check if the given field is used as col group by\n */\n _isCol(field) {\n return this.metaData.fullColGroupBys\n .map(this.parseGroupField)\n .map(({ field }) => field.name)\n .includes(field.name);\n }\n\n /**\n * Check if the given field is used as row group by\n */\n _isRow(field) {\n return this.metaData.fullRowGroupBys\n .map(this.parseGroupField)\n .map(({ field }) => field.name)\n .includes(field.name);\n }\n\n /**\n * Get the value of a field-value for a positional group by\n *\n * @param {object} field Field of the group by\n * @param {unknown} groupValueString Value of the group by\n * @param {(number | boolean | string)[]} rows Values for the previous row group bys\n * @param {(number | boolean | string)[]} cols Values for the previous col group bys\n *\n * @private\n * @returns {number | boolean | string}\n */\n _parsePivotFormulaWithPosition(field, groupValueString, cols, rows) {\n const position = toNumber(groupValueString) - 1;\n let tree;\n if (this._isCol(field)) {\n tree = this.data.colGroupTree;\n for (const col of cols) {\n tree = tree && tree.directSubTrees.get(col);\n }\n } else {\n tree = this.data.rowGroupTree;\n for (const row of rows) {\n tree = tree && tree.directSubTrees.get(row);\n }\n }\n if (tree) {\n const treeKeys = tree.sortedKeys || [...tree.directSubTrees.keys()];\n const sortedKey = treeKeys[position];\n return sortedKey !== undefined ? sortedKey : NO_RECORD_AT_THIS_POSITION;\n }\n return NO_RECORD_AT_THIS_POSITION;\n }\n\n /**\n * Transform the given domain in the structure used in this class\n *\n * @param {(number | boolean | string)[]} domain Domain\n *\n * @private\n */\n _getColsRowsValuesFromDomain(domain) {\n const rows = [];\n const cols = [];\n let i = 0;\n while (i < domain.length) {\n const groupFieldString = domain[i];\n const groupValue = domain[i + 1];\n const { field, isPositional } = this.parseGroupField(groupFieldString);\n let value;\n if (isPositional) {\n value = this._parsePivotFormulaWithPosition(field, groupValue, cols, rows);\n } else {\n value = parsePivotFormulaFieldValue(field, groupValue);\n }\n if (this._isCol(field)) {\n cols.push(value);\n } else if (this._isRow(field)) {\n rows.push(value);\n }\n i += 2;\n }\n return { rows, cols };\n }\n\n /**\n * Get the row structure\n * @returns {Row[]}\n */\n _getSpreadsheetRows(tree) {\n /**@type {Row[]}*/\n let rows = [];\n const group = tree.root;\n const indent = group.labels.length;\n const rowGroupBys = this.metaData.fullRowGroupBys;\n\n rows.push({\n fields: rowGroupBys.slice(0, indent),\n values: group.values.map((val) => val.toString()),\n indent,\n });\n\n const subTreeKeys = tree.sortedKeys || [...tree.directSubTrees.keys()];\n subTreeKeys.forEach((subTreeKey) => {\n const subTree = tree.directSubTrees.get(subTreeKey);\n rows = rows.concat(this._getSpreadsheetRows(subTree));\n });\n return rows;\n }\n\n /**\n * Get the col structure\n * @returns {Column[][]}\n */\n _getSpreadsheetCols() {\n const colGroupBys = this.metaData.fullColGroupBys;\n const height = colGroupBys.length;\n const measureCount = this.metaData.activeMeasures.length;\n const leafCounts = this._getLeafCounts(this.data.colGroupTree);\n\n const headers = new Array(height).fill(0).map(() => []);\n\n function generateTreeHeaders(tree, fields) {\n const group = tree.root;\n const rowIndex = group.values.length;\n if (rowIndex !== 0) {\n const row = headers[rowIndex - 1];\n const leafCount = leafCounts[JSON.stringify(tree.root.values)];\n const cell = {\n fields: colGroupBys.slice(0, rowIndex),\n values: group.values.map((val) => val.toString()),\n width: leafCount * measureCount,\n };\n row.push(cell);\n }\n\n [...tree.directSubTrees.values()].forEach((subTree) => {\n generateTreeHeaders(subTree, fields);\n });\n }\n\n generateTreeHeaders(this.data.colGroupTree, this.metaData.fields);\n const hasColGroupBys = this.metaData.colGroupBys.length;\n\n // 2) generate measures row\n const measureRow = [];\n\n if (hasColGroupBys) {\n headers[headers.length - 1].forEach((cell) => {\n this.metaData.activeMeasures.forEach((measureName) => {\n const measureCell = {\n fields: [...cell.fields, \"measure\"],\n values: [...cell.values, measureName],\n width: 1,\n };\n measureRow.push(measureCell);\n });\n });\n }\n this.metaData.activeMeasures.forEach((measureName) => {\n const measureCell = {\n fields: [\"measure\"],\n values: [measureName],\n width: 1,\n };\n measureRow.push(measureCell);\n });\n headers.push(measureRow);\n // 3) Add the total cell\n if (headers.length === 1) {\n headers.unshift([]); // Will add the total there\n }\n headers[headers.length - 2].push({\n fields: [],\n values: [],\n width: this.metaData.activeMeasures.length,\n });\n\n return headers;\n }\n}\n", "/** @odoo-module */\n\n/**\n * @typedef {Object} Column\n * @property {string[]} fields\n * @property {string[]} values\n * @property {number} width\n *\n * @typedef {Object} Row\n * @property {string[]} fields\n * @property {string[]} values\n * @property {number} intend\n *\n * @typedef {Object} SpreadsheetTableData\n * @property {Column[][]} cols\n * @property {Row[]} rows\n * @property {string[]} measures\n */\n\n/**\n * Class used to ease the construction of a pivot table.\n * Let's consider the following example, with:\n * - columns groupBy: [sales_team, create_date]\n * - rows groupBy: [continent, city]\n * - measures: [revenues]\n * _____________________________________________________________________________________| ----|\n * | | Sale Team 1 | Sale Team 2 | | |\n * | |___________________________|_________________________|_____________| |\n * | | May 2020 | June 2020 | May 2020 | June 2020 | Total | |<---- `cols`\n * | |______________|____________|____________|____________|_____________| | ----|\n * | | Revenues | Revenues | Revenues | Revenues | Revenues | | |<--- `measureRow`\n * |________________|______________|____________|____________|____________|_____________| ----| ----|\n * |Europe | 25 | 35 | 40 | 30 | 65 | ----|\n * | Brussels | 0 | 15 | 30 | 30 | 30 | |\n * | Paris | 25 | 20 | 10 | 0 | 35 | |\n * |North America | 60 | 75 | | | 60 | |<---- `body`\n * | Washington | 60 | 75 | | | 60 | |\n * |Total | 85 | 110 | 40 | 30 | 125 | |\n * |________________|______________|____________|____________|____________|_____________| ----|\n *\n * | |\n * |----------------|\n * |\n * |\n * `rows`\n *\n * `rows` is an array of cells, each cells contains the indent level, the fields used for the group by and the values for theses fields.\n * For example:\n * `Europe`: { indent: 1, fields: [\"continent\"], values: [\"id_of_Europe\"]}\n * `Brussels`: { indent: 2, fields: [\"continent\", \"city\"], values: [\"id_of_Europe\", \"id_of_Brussels\"]}\n * `Total`: { indent: 0, fields: [], values: []}\n *\n * `columns` is an double array, first by row and then by cell. So, in this example, it looks like:\n * [[row1], [row2], [measureRow]]\n * Each cell of a column's row contains the width (span) of the cells, the fields used for the group by and the values for theses fields.\n * For example:\n * `Sale Team 1`: { width: 2, fields: [\"sales_team\"], values: [\"id_of_SaleTeam1\"]}\n * `May 2020` (the one under Sale Team 2): { width: 1, fields: [\"sales_team\", \"create_date\"], values: [\"id_of_SaleTeam2\", \"May 2020\"]}\n * `Revenues` (the one under Total): { width: 1, fields: [\"measure\"], values: [\"revenues\"]}\n *\n */\nexport class SpreadsheetPivotTable {\n /**\n * @param {Column[][]} cols\n * @param {Row[]} rows\n * @param {string[]} measures\n */\n constructor(cols, rows, measures) {\n this._cols = cols;\n this._rows = rows;\n this._measures = measures;\n }\n\n /**\n * @returns {number}\n */\n getNumberOfMeasures() {\n return this._measures.length;\n }\n\n /**\n * @returns {Column[][]}\n */\n getColHeaders() {\n return this._cols;\n }\n\n /**\n * Get the last row of the columns (i.e. the one with the measures)\n * @returns {Column[]}\n */\n getMeasureHeaders() {\n return this._cols[this._cols.length - 1];\n }\n\n /**\n * Get the number of columns leafs (i.e. the number of the last row of columns)\n * @returns {number}\n */\n getColWidth() {\n return this._cols[this._cols.length - 1].length;\n }\n\n /**\n * Get the number of row in each columns\n * @return {number}\n */\n getColHeight() {\n return this._cols.length;\n }\n\n /**\n * @returns {Row[]}\n */\n getRowHeaders() {\n return this._rows;\n }\n\n /**\n * Get the number of rows\n *\n * @returns {number}\n */\n getRowHeight() {\n return this._rows.length;\n }\n\n /**\n * Get the index of the cell in the measure row (i.e. the last one) which\n * correspond to the given values\n *\n * @returns {number}\n */\n getColMeasureIndex(values) {\n const vals = JSON.stringify(values);\n const maxLength = Math.max(...this._cols.map((col) => col.length));\n for (let i = 0; i < maxLength; i++) {\n const cellValues = this._cols.map((col) => JSON.stringify((col[i] || {}).values));\n if (cellValues.includes(vals)) {\n return i;\n }\n }\n return -1;\n }\n\n /**\n *\n * @param {number} colIndex\n * @param {number} rowIndex\n * @returns {Column}\n */\n getNextColCell(colIndex, rowIndex) {\n return this._cols[rowIndex][colIndex];\n }\n\n getRowIndex(values) {\n const vals = JSON.stringify(values);\n return this._rows.findIndex(\n (cell) => JSON.stringify(cell.values.map((val) => val.toString())) === vals\n );\n }\n\n getCellFromMeasureRowAtIndex(index) {\n return this.getMeasureHeaders()[index];\n }\n\n getCellsFromRowAtIndex(index) {\n return this._rows[index];\n }\n\n /**\n * @returns {SpreadsheetTableData}\n */\n export() {\n return {\n cols: this._cols,\n rows: this._rows,\n measures: this._measures,\n };\n }\n}\n", "/** @odoo-module */\n\n/**\n *\n * @typedef {Object} PivotDefinition\n * @property {Array} colGroupBys\n * @property {Array} rowGroupBys\n * @property {Array} measures\n * @property {string} model\n * @property {Array} domain\n * @property {Object} context\n * @property {string} name\n * @property {string} id\n * @property {Object | null} sortedColumn\n *\n * @typedef {Object} Pivot\n * @property {string} id\n * @property {string} dataSourceId\n * @property {PivotDefinition} definition\n * @property {Object} fieldMatching\n *\n * @typedef {import(\"@spreadsheet/global_filters/plugins/global_filters_core_plugin\").FieldMatching} FieldMatching\n */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { makePivotFormula } from \"../pivot_helpers\";\nimport { getMaxObjectId } from \"@spreadsheet/helpers/helpers\";\nimport { HEADER_STYLE, TOP_LEVEL_STYLE, MEASURE_STYLE } from \"@spreadsheet/helpers/constants\";\nimport PivotDataSource from \"../pivot_data_source\";\nimport { SpreadsheetPivotTable } from \"../pivot_table\";\nimport CommandResult from \"../../o_spreadsheet/cancelled_reason\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { globalFiltersFieldMatchers } from \"@spreadsheet/global_filters/plugins/global_filters_core_plugin\";\nimport { sprintf } from \"@web/core/utils/strings\";\nimport { checkFilterFieldMatching } from \"@spreadsheet/global_filters/helpers\";\n\nconst { CorePlugin } = spreadsheet;\n\nexport default class PivotCorePlugin extends CorePlugin {\n constructor(config) {\n super(config);\n this.dataSources = config.custom.dataSources;\n\n this.nextId = 1;\n /** @type {Object.} */\n this.pivots = {};\n globalFiltersFieldMatchers[\"pivot\"] = {\n geIds: () => this.getters.getPivotIds(),\n getDisplayName: (pivotId) => this.getters.getPivotName(pivotId),\n getTag: (pivotId) => sprintf(_t(\"Pivot #%s\"), pivotId),\n getFieldMatching: (pivotId, filterId) => this.getPivotFieldMatching(pivotId, filterId),\n waitForReady: () => this.getPivotsWaitForReady(),\n getModel: (pivotId) => this.getPivotDefinition(pivotId).model,\n getFields: (pivotId) => this.getPivotDataSource(pivotId).getFields(),\n };\n }\n\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"RENAME_ODOO_PIVOT\":\n if (!(cmd.pivotId in this.pivots)) {\n return CommandResult.PivotIdNotFound;\n }\n if (cmd.name === \"\") {\n return CommandResult.EmptyName;\n }\n break;\n case \"INSERT_PIVOT\":\n if (cmd.id !== this.nextId.toString()) {\n return CommandResult.InvalidNextId;\n }\n break;\n case \"ADD_GLOBAL_FILTER\":\n case \"EDIT_GLOBAL_FILTER\":\n if (cmd.pivot) {\n return checkFilterFieldMatching(cmd.pivot);\n }\n }\n return CommandResult.Success;\n }\n\n /**\n * Handle a spreadsheet command\n *\n * @param {Object} cmd Command\n */\n handle(cmd) {\n switch (cmd.type) {\n case \"INSERT_PIVOT\": {\n const { sheetId, col, row, id, definition, dataSourceId } = cmd;\n /** @type [number,number] */\n const anchor = [col, row];\n const { cols, rows, measures } = cmd.table;\n const table = new SpreadsheetPivotTable(cols, rows, measures);\n this._addPivot(id, definition, dataSourceId);\n this._insertPivot(sheetId, anchor, id, table);\n this.history.update(\"nextId\", parseInt(id, 10) + 1);\n break;\n }\n case \"RE_INSERT_PIVOT\": {\n const { sheetId, col, row, id } = cmd;\n /** @type [number,number] */\n const anchor = [col, row];\n const { cols, rows, measures } = cmd.table;\n const table = new SpreadsheetPivotTable(cols, rows, measures);\n this._insertPivot(sheetId, anchor, id, table);\n break;\n }\n case \"RENAME_ODOO_PIVOT\": {\n this.history.update(\"pivots\", cmd.pivotId, \"definition\", \"name\", cmd.name);\n break;\n }\n case \"REMOVE_PIVOT\": {\n const pivots = { ...this.pivots };\n delete pivots[cmd.pivotId];\n this.history.update(\"pivots\", pivots);\n break;\n }\n case \"UPDATE_ODOO_PIVOT_DOMAIN\": {\n this.history.update(\n \"pivots\",\n cmd.pivotId,\n \"definition\",\n \"searchParams\",\n \"domain\",\n cmd.domain\n );\n const pivot = this.pivots[cmd.pivotId];\n this.dataSources.add(pivot.dataSourceId, PivotDataSource, pivot.definition);\n break;\n }\n case \"UNDO\":\n case \"REDO\": {\n const domainEditionCommands = cmd.commands.filter(\n (cmd) => cmd.type === \"UPDATE_ODOO_PIVOT_DOMAIN\"\n );\n for (const cmd of domainEditionCommands) {\n const pivot = this.pivots[cmd.pivotId];\n this.dataSources.add(pivot.dataSourceId, PivotDataSource, pivot.definition);\n }\n break;\n }\n case \"ADD_GLOBAL_FILTER\":\n case \"EDIT_GLOBAL_FILTER\":\n if (cmd.pivot) {\n this._setPivotFieldMatching(cmd.filter.id, cmd.pivot);\n }\n break;\n case \"REMOVE_GLOBAL_FILTER\":\n this._onFilterDeletion(cmd.id);\n break;\n }\n }\n\n // -------------------------------------------------------------------------\n // Getters\n // -------------------------------------------------------------------------\n\n /**\n * @param {string} id\n * @returns {PivotDataSource|undefined}\n */\n getPivotDataSource(id) {\n const dataSourceId = this.pivots[id].dataSourceId;\n return this.dataSources.get(dataSourceId);\n }\n\n /**\n * @param {string} id\n * @returns {string}\n */\n getPivotDisplayName(id) {\n return `(#${id}) ${this.getPivotName(id)}`;\n }\n\n /**\n * @param {string} id\n * @returns {string}\n */\n getPivotName(id) {\n return _t(this.pivots[id].definition.name);\n }\n\n /**\n * @param {string} id\n * @returns {string}\n */\n getPivotFieldMatch(id) {\n return this.pivots[id].fieldMatching;\n }\n\n /**\n * @param {string} id\n * @returns {Promise}\n */\n async getAsyncPivotDataSource(id) {\n const dataSourceId = this.pivots[id].dataSourceId;\n await this.dataSources.load(dataSourceId);\n return this.getPivotDataSource(id);\n }\n\n /**\n * Retrieve the next available id for a new pivot\n *\n * @returns {string} id\n */\n getNextPivotId() {\n return this.nextId.toString();\n }\n\n /**\n * @param {string} id Id of the pivot\n *\n * @returns {PivotDefinition}\n */\n getPivotDefinition(id) {\n const def = this.pivots[id].definition;\n return {\n colGroupBys: [...def.metaData.colGroupBys],\n context: { ...def.searchParams.context },\n domain: [...def.searchParams.domain],\n id,\n measures: [...def.metaData.activeMeasures],\n model: def.metaData.resModel,\n rowGroupBys: [...def.metaData.rowGroupBys],\n name: def.name,\n sortedColumn: def.metaData.sortedColumn ? { ...def.metaData.sortedColumn } : null,\n };\n }\n\n /**\n * Retrieve all the pivot ids\n *\n * @returns {Array}\n */\n getPivotIds() {\n return Object.keys(this.pivots);\n }\n\n /**\n * Check if an id is an id of an existing pivot\n *\n * @param {number} pivotId Id of the pivot\n *\n * @returns {boolean}\n */\n isExistingPivot(pivotId) {\n return pivotId in this.pivots;\n }\n\n /**\n * Get the current pivotFieldMatching on a pivot\n *\n * @param {string} pivotId\n * @param {string} filterId\n */\n getPivotFieldMatching(pivotId, filterId) {\n return this.pivots[pivotId].fieldMatching[filterId];\n }\n\n // -------------------------------------------------------------------------\n // Private\n // -------------------------------------------------------------------------\n\n /**\n *\n * @return {Promise[]}\n */\n getPivotsWaitForReady() {\n return this.getPivotIds().map((pivotId) => this.getPivotDataSource(pivotId).loadMetadata());\n }\n\n /**\n * Sets the current pivotFieldMatching on a pivot\n *\n * @param {string} filterId\n * @param {Record} pivotFieldMatches\n */\n _setPivotFieldMatching(filterId, pivotFieldMatches) {\n const pivots = { ...this.pivots };\n for (const [pivotId, fieldMatch] of Object.entries(pivotFieldMatches)) {\n pivots[pivotId].fieldMatching[filterId] = fieldMatch;\n }\n this.history.update(\"pivots\", pivots);\n }\n\n _onFilterDeletion(filterId) {\n const pivots = { ...this.pivots };\n for (const pivotId in pivots) {\n this.history.update(\"pivots\", pivotId, \"fieldMatching\", filterId, undefined);\n }\n }\n\n /**\n * @param {string} id\n * @param {PivotDefinition} definition\n * @param {string} dataSourceId\n */\n _addPivot(id, definition, dataSourceId, fieldMatching = {}) {\n const pivots = { ...this.pivots };\n pivots[id] = {\n id,\n definition,\n dataSourceId,\n fieldMatching,\n };\n\n if (!this.dataSources.contains(dataSourceId)) {\n this.dataSources.add(dataSourceId, PivotDataSource, definition);\n }\n this.history.update(\"pivots\", pivots);\n }\n\n /**\n * @param {string} sheetId\n * @param {[number, number]} anchor\n * @param {string} id\n * @param {SpreadsheetPivotTable} table\n */\n _insertPivot(sheetId, anchor, id, table) {\n this._resizeSheet(sheetId, anchor, table);\n this._insertColumns(sheetId, anchor, id, table);\n this._insertRows(sheetId, anchor, id, table);\n this._insertBody(sheetId, anchor, id, table);\n }\n\n /**\n * @param {string} sheetId\n * @param {[number, number]} anchor\n * @param {string} id\n * @param {SpreadsheetPivotTable} table\n */\n _insertColumns(sheetId, anchor, id, table) {\n let anchorLeft = anchor[0] + 1;\n let anchorTop = anchor[1];\n for (const _row of table.getColHeaders()) {\n anchorLeft = anchor[0] + 1;\n for (const cell of _row) {\n const args = [id];\n for (let i = 0; i < cell.fields.length; i++) {\n args.push(cell.fields[i]);\n args.push(cell.values[i]);\n }\n if (cell.width > 1) {\n this._merge(sheetId, {\n top: anchorTop,\n bottom: anchorTop,\n left: anchorLeft,\n right: anchorLeft + cell.width - 1,\n });\n }\n this._addPivotFormula(sheetId, anchorLeft, anchorTop, \"ODOO.PIVOT.HEADER\", args);\n anchorLeft += cell.width;\n }\n anchorTop++;\n }\n const colHeight = table.getColHeight();\n const colWidth = table.getColWidth();\n const lastRowBeforeMeasureRow = anchor[1] + colHeight - 2;\n const right = anchor[0] + colWidth;\n const left = right - table.getNumberOfMeasures() + 1;\n for (let anchorTop = anchor[1]; anchorTop < lastRowBeforeMeasureRow; anchorTop++) {\n this._merge(sheetId, { top: anchorTop, bottom: anchorTop, left, right });\n }\n const headersZone = {\n top: anchor[1],\n bottom: lastRowBeforeMeasureRow,\n left: anchor[0],\n right: anchor[0] + colWidth,\n };\n const measuresZone = {\n top: anchor[1] + colHeight - 1,\n bottom: anchor[1] + colHeight - 1,\n left: anchor[0],\n right: anchor[0] + colWidth,\n };\n this.dispatch(\"SET_FORMATTING\", { sheetId, target: [headersZone], style: TOP_LEVEL_STYLE });\n this.dispatch(\"SET_FORMATTING\", { sheetId, target: [measuresZone], style: MEASURE_STYLE });\n }\n\n /**\n * Merge a zone\n *\n * @param {string} sheetId\n * @param {Object} zone\n *\n * @private\n */\n _merge(sheetId, zone) {\n this.dispatch(\"ADD_MERGE\", { sheetId, target: [zone] });\n }\n\n /**\n * @param {string} sheetId\n * @param {[number,number]} anchor\n * @param {SpreadsheetPivotTable} table\n */\n _resizeSheet(sheetId, anchor, table) {\n const colLimit = table.getColWidth() + 1; // +1 for the Top-Left\n const numberCols = this.getters.getNumberCols(sheetId);\n const deltaCol = numberCols - anchor[0];\n if (deltaCol < colLimit) {\n this.dispatch(\"ADD_COLUMNS_ROWS\", {\n dimension: \"COL\",\n base: numberCols - 1,\n sheetId: sheetId,\n quantity: colLimit - deltaCol,\n position: \"after\",\n });\n }\n const rowLimit = table.getColHeight() + table.getRowHeight();\n const numberRows = this.getters.getNumberRows(sheetId);\n const deltaRow = numberRows - anchor[1];\n if (deltaRow < rowLimit) {\n this.dispatch(\"ADD_COLUMNS_ROWS\", {\n dimension: \"ROW\",\n base: numberRows - 1,\n sheetId: sheetId,\n quantity: rowLimit - deltaRow,\n position: \"after\",\n });\n }\n }\n\n /**\n * @param {string} sheetId\n * @param {[number, number]} anchor\n * @param {string} id\n * @param {SpreadsheetPivotTable} table\n */\n _insertRows(sheetId, anchor, id, table) {\n let y = anchor[1] + table.getColHeight();\n const x = anchor[0];\n for (const row of table.getRowHeaders()) {\n const args = [id];\n for (let i = 0; i < row.fields.length; i++) {\n args.push(row.fields[i]);\n args.push(row.values[i]);\n }\n this._addPivotFormula(sheetId, x, y, \"ODOO.PIVOT.HEADER\", args);\n if (row.indent <= 2) {\n const target = [{ top: y, bottom: y, left: x, right: x }];\n const style = row.indent === 2 ? HEADER_STYLE : TOP_LEVEL_STYLE;\n this.dispatch(\"SET_FORMATTING\", { sheetId, target, style });\n }\n y++;\n }\n }\n\n /**\n * @param {string} sheetId\n * @param {[number, number]} anchor\n * @param {string} id\n * @param {SpreadsheetPivotTable} table\n */\n _insertBody(sheetId, anchor, id, table) {\n let x = anchor[0] + 1;\n for (const col of table.getMeasureHeaders()) {\n let y = anchor[1] + table.getColHeight();\n const measure = col.values[col.values.length - 1];\n for (const row of table.getRowHeaders()) {\n const args = [id, measure];\n for (let i = 0; i < row.fields.length; i++) {\n args.push(row.fields[i]);\n args.push(row.values[i]);\n }\n for (let i = 0; i < col.fields.length - 1; i++) {\n args.push(col.fields[i]);\n args.push(col.values[i]);\n }\n this._addPivotFormula(sheetId, x, y, \"ODOO.PIVOT\", args);\n y++;\n }\n x++;\n }\n }\n\n /**\n * @param {string} sheetId\n * @param {number} col\n * @param {number} row\n * @param {string} formula\n * @param {Array} args\n */\n _addPivotFormula(sheetId, col, row, formula, args) {\n this.dispatch(\"UPDATE_CELL\", {\n sheetId,\n col,\n row,\n content: makePivotFormula(formula, args),\n });\n }\n\n // ---------------------------------------------------------------------\n // Import/Export\n // ---------------------------------------------------------------------\n\n /**\n * Import the pivots\n *\n * @param {Object} data\n */\n import(data) {\n if (data.pivots) {\n for (const [id, pivot] of Object.entries(data.pivots)) {\n const definition = {\n metaData: {\n colGroupBys: pivot.colGroupBys,\n rowGroupBys: pivot.rowGroupBys,\n activeMeasures: pivot.measures.map((elt) => elt.field),\n resModel: pivot.model,\n sortedColumn: !pivot.sortedColumn\n ? undefined\n : {\n groupId: pivot.sortedColumn.groupId,\n measure: pivot.sortedColumn.measure,\n order: pivot.sortedColumn.order,\n },\n },\n searchParams: {\n groupBy: [],\n orderBy: [],\n domain: pivot.domain,\n context: pivot.context,\n },\n name: pivot.name,\n };\n this._addPivot(id, definition, this.uuidGenerator.uuidv4(), pivot.fieldMatching);\n }\n }\n this.nextId = data.pivotNextId || getMaxObjectId(this.pivots) + 1;\n }\n /**\n * Export the pivots\n *\n * @param {Object} data\n */\n export(data) {\n data.pivots = {};\n for (const id in this.pivots) {\n data.pivots[id] = JSON.parse(JSON.stringify(this.getPivotDefinition(id)));\n data.pivots[id].measures = data.pivots[id].measures.map((elt) => ({ field: elt }));\n data.pivots[id].fieldMatching = this.pivots[id].fieldMatching;\n }\n data.pivotNextId = this.nextId;\n }\n}\n\nPivotCorePlugin.getters = [\n \"getNextPivotId\",\n \"getPivotDefinition\",\n \"getPivotDisplayName\",\n \"getPivotIds\",\n \"getPivotName\",\n \"getAsyncPivotDataSource\",\n \"isExistingPivot\",\n \"getPivotDataSource\",\n \"getPivotFieldMatch\",\n \"getPivotFieldMatching\",\n];\n", "/** @odoo-module */\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { getFirstPivotFunction } from \"../pivot_helpers\";\nimport { FILTER_DATE_OPTION, monthsOptions } from \"@spreadsheet/assets_backend/constants\";\nimport { Domain } from \"@web/core/domain\";\nimport { NO_RECORD_AT_THIS_POSITION } from \"../pivot_model\";\n\nconst { astToFormula } = spreadsheet;\nconst { DateTime } = luxon;\n\n/**\n * Convert pivot period to the related filter value\n *\n * @param {import(\"@spreadsheet/global_filters/plugins/global_filters_core_plugin\").RangeType} timeRange\n * @param {string} value\n * @returns {object}\n */\nfunction pivotPeriodToFilterValue(timeRange, value) {\n // reuse the same logic as in `parseAccountingDate`?\n const yearOffset = (value.split(\"/\").pop() | 0) - DateTime.now().year;\n switch (timeRange) {\n case \"year\":\n return {\n yearOffset,\n };\n case \"month\": {\n const month = value.split(\"/\")[0] | 0;\n return {\n yearOffset,\n period: monthsOptions[month - 1].id,\n };\n }\n case \"quarter\": {\n const quarter = value.split(\"/\")[0] | 0;\n return {\n yearOffset,\n period: FILTER_DATE_OPTION.quarter[quarter - 1],\n };\n }\n }\n}\n\nexport default class PivotUIPlugin extends spreadsheet.UIPlugin {\n constructor(config) {\n super(config);\n /** @type {string} */\n this.selectedPivotId = undefined;\n this.selection.observe(this, {\n handleEvent: this.handleEvent.bind(this),\n });\n }\n\n handleEvent(event) {\n if (!this.getters.isDashboard()) {\n return;\n }\n switch (event.type) {\n case \"ZonesSelected\": {\n const sheetId = this.getters.getActiveSheetId();\n const { col, row } = event.anchor.cell;\n const cell = this.getters.getCell({ sheetId, col, row });\n if (cell !== undefined && cell.content.startsWith(\"=ODOO.PIVOT.HEADER(\")) {\n const filters = this.getFiltersMatchingPivot(cell.content);\n this.dispatch(\"SET_MANY_GLOBAL_FILTER_VALUE\", { filters });\n }\n break;\n }\n }\n }\n\n beforeHandle(cmd) {\n switch (cmd.type) {\n case \"START\":\n // make sure the domains are correctly set before\n // any evaluation\n this._addDomains();\n break;\n }\n }\n\n /**\n * Handle a spreadsheet command\n * @param {Object} cmd Command\n */\n handle(cmd) {\n switch (cmd.type) {\n case \"SELECT_PIVOT\":\n this.selectedPivotId = cmd.pivotId;\n break;\n case \"REFRESH_PIVOT\":\n this._refreshOdooPivot(cmd.id);\n break;\n case \"REFRESH_ALL_DATA_SOURCES\":\n this._refreshOdooPivots();\n break;\n case \"ADD_GLOBAL_FILTER\":\n case \"EDIT_GLOBAL_FILTER\":\n case \"REMOVE_GLOBAL_FILTER\":\n case \"SET_GLOBAL_FILTER_VALUE\":\n case \"CLEAR_GLOBAL_FILTER_VALUE\":\n this._addDomains();\n break;\n case \"UNDO\":\n case \"REDO\":\n if (\n cmd.commands.find((command) =>\n [\n \"ADD_GLOBAL_FILTER\",\n \"EDIT_GLOBAL_FILTER\",\n \"REMOVE_GLOBAL_FILTER\",\n ].includes(command.type)\n )\n ) {\n this._addDomains();\n }\n break;\n }\n }\n\n // ---------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------\n\n /**\n * Retrieve the pivotId of the current selected cell\n *\n * @returns {string}\n */\n getSelectedPivotId() {\n return this.selectedPivotId;\n }\n\n /**\n * Get the id of the pivot at the given position. Returns undefined if there\n * is no pivot at this position\n *\n * @param {{ sheetId: string; col: number; row: number}} position\n *\n * @returns {string|undefined}\n */\n getPivotIdFromPosition(position) {\n const cell = this.getters.getCell(position);\n if (cell && cell.isFormula) {\n const pivotFunction = getFirstPivotFunction(cell.content);\n if (pivotFunction) {\n const content = astToFormula(pivotFunction.args[0]);\n return this.getters.evaluateFormula(content).toString();\n }\n }\n return undefined;\n }\n\n /**\n * Get the computed domain of a pivot\n * CLEAN ME not used outside of tests\n * @param {string} pivotId Id of the pivot\n * @returns {Array}\n */\n getPivotComputedDomain(pivotId) {\n return this.getters.getPivotDataSource(pivotId).getComputedDomain();\n }\n\n /**\n * Return all possible values in the pivot for a given field.\n *\n * @param {string} pivotId Id of the pivot\n * @param {string} fieldName\n * @returns {Array}\n */\n getPivotGroupByValues(pivotId, fieldName) {\n return this.getters.getPivotDataSource(pivotId).getPossibleValuesForGroupBy(fieldName);\n }\n\n /**\n * Get the value of a pivot header\n *\n * @param {string} pivotId Id of a pivot\n * @param {Array} domain Domain\n */\n getDisplayedPivotHeaderValue(pivotId, domain) {\n const dataSource = this.getters.getPivotDataSource(pivotId);\n dataSource.markAsHeaderUsed(domain);\n const len = domain.length;\n if (len === 0) {\n return _t(\"Total\");\n }\n return dataSource.getDisplayedPivotHeaderValue(domain);\n }\n\n /**\n * Get the value for a pivot cell\n *\n * @param {string} pivotId Id of a pivot\n * @param {string} measure Field name of the measures\n * @param {Array} domain Domain\n *\n * @returns {string|number|undefined}\n */\n getPivotCellValue(pivotId, measure, domain) {\n const dataSource = this.getters.getPivotDataSource(pivotId);\n dataSource.markAsValueUsed(domain, measure);\n return dataSource.getPivotCellValue(measure, domain);\n }\n\n /**\n * Get the filter impacted by a pivot formula's argument\n *\n * @param {string} formula Formula of the pivot cell\n *\n * @returns {Array}\n */\n getFiltersMatchingPivot(formula) {\n const functionDescription = getFirstPivotFunction(formula);\n if (!functionDescription) {\n return [];\n }\n const { args } = functionDescription;\n const evaluatedArgs = args\n .map(astToFormula)\n .map((arg) => this.getters.evaluateFormula(arg));\n if (evaluatedArgs.length <= 2) {\n return [];\n }\n const pivotId = evaluatedArgs[0];\n const argField = evaluatedArgs[evaluatedArgs.length - 2];\n if (argField === \"measure\") {\n return [];\n }\n const filters = this.getters.getGlobalFilters();\n const matchingFilters = [];\n\n for (const filter of filters) {\n const dataSource = this.getters.getPivotDataSource(pivotId);\n const { field, aggregateOperator: time } = dataSource.parseGroupField(argField);\n const pivotFieldMatching = this.getters.getPivotFieldMatching(pivotId, filter.id);\n if (pivotFieldMatching && pivotFieldMatching.chain === field.name) {\n let value = dataSource.getPivotHeaderValue(evaluatedArgs.slice(-2));\n if (value === NO_RECORD_AT_THIS_POSITION) {\n continue;\n }\n let transformedValue;\n const currentValue = this.getters.getGlobalFilterValue(filter.id);\n switch (filter.type) {\n case \"date\":\n if (time === filter.rangeType) {\n transformedValue = pivotPeriodToFilterValue(time, value);\n if (JSON.stringify(transformedValue) === JSON.stringify(currentValue)) {\n transformedValue = undefined;\n }\n } else {\n continue;\n }\n break;\n case \"relation\":\n if (typeof value == \"string\") {\n value = Number(value);\n if (Number.isNaN(value)) {\n break;\n }\n }\n if (JSON.stringify(currentValue) !== `[${value}]`) {\n transformedValue = [value];\n }\n break;\n case \"text\":\n if (currentValue !== value) {\n transformedValue = value;\n }\n break;\n }\n matchingFilters.push({ filterId: filter.id, value: transformedValue });\n }\n }\n return matchingFilters;\n }\n\n // ---------------------------------------------------------------------\n // Private\n // ---------------------------------------------------------------------\n\n /**\n * Refresh the cache of a pivot\n *\n * @param {string} pivotId Id of the pivot\n */\n _refreshOdooPivot(pivotId) {\n const dataSource = this.getters.getPivotDataSource(pivotId);\n dataSource.clearUsedValues();\n dataSource.load({ reload: true });\n }\n\n /**\n * Refresh the cache of all the pivots\n */\n _refreshOdooPivots() {\n for (const pivotId of this.getters.getPivotIds()) {\n this._refreshOdooPivot(pivotId, false);\n }\n }\n\n /**\n * Add an additional domain to a pivot\n *\n * @private\n *\n * @param {string} pivotId pivot id\n */\n _addDomain(pivotId) {\n const domainList = [];\n for (const [filterId, fieldMatch] of Object.entries(\n this.getters.getPivotFieldMatch(pivotId)\n )) {\n domainList.push(this.getters.getGlobalFilterDomain(filterId, fieldMatch));\n }\n const domain = Domain.combine(domainList, \"AND\").toString();\n this.getters.getPivotDataSource(pivotId).addDomain(domain);\n }\n\n /**\n * Add an additional domain to all pivots\n *\n * @private\n *\n */\n _addDomains() {\n for (const pivotId of this.getters.getPivotIds()) {\n this._addDomain(pivotId);\n }\n }\n}\n\nPivotUIPlugin.getters = [\n \"getSelectedPivotId\",\n \"getPivotComputedDomain\",\n \"getDisplayedPivotHeaderValue\",\n \"getPivotIdFromPosition\",\n \"getPivotCellValue\",\n \"getPivotGroupByValues\",\n \"getFiltersMatchingPivot\",\n];\n", "/** @odoo-module **/\nimport { useService } from \"@web/core/utils/hooks\";\nimport { useSetupAction } from \"@web/webclient/actions/action_hook\";\n\nimport { UNTITLED_SPREADSHEET_NAME } from \"@spreadsheet/helpers/constants\";\nimport { initCallbackRegistry } from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nimport { LegacyComponent } from \"@web/legacy/legacy_component\";\nimport { loadSpreadsheetDependencies } from \"@spreadsheet/helpers/helpers\";\n\n/**\n * @typedef SpreadsheetRecord\n * @property {number} id\n * @property {string} name\n * @property {string} raw\n * @property {Object[]} revisions\n * @property {boolean} snapshot_requested\n * @property {Boolean} isReadonly\n */\n\nconst { onMounted, onWillStart, useState } = owl;\nexport class AbstractSpreadsheetAction extends LegacyComponent {\n setup() {\n if (!this.props.action.params) {\n // the action is coming from a this.trigger(\"do-action\", ... ) of owl (not wowl and not legacy)\n this.params = this.props.action.context;\n } else {\n // the action is coming from wowl\n this.params = this.props.action.params;\n }\n this.isEmptySpreadsheet = this.params.is_new_spreadsheet || false;\n this.resId =\n this.params.spreadsheet_id ||\n this.params.active_id || // backward compatibility. spreadsheet_id used to be active_id\n (this.props.state && this.props.state.resId); // used when going back to a spreadsheet via breadcrumb\n this.router = useService(\"router\");\n this.actionService = useService(\"action\");\n this.notifications = useService(\"notification\");\n this.orm = useService(\"orm\");\n this.http = useService(\"http\");\n useSetupAction({\n getLocalState: () => {\n return {\n resId: this.resId,\n };\n },\n });\n this.state = useState({\n spreadsheetName: UNTITLED_SPREADSHEET_NAME,\n });\n\n onWillStart(() => this.onWillStart());\n onMounted(() => this.onMounted());\n }\n\n async onWillStart() {\n // if we are returning to the spreadsheet via the breadcrumb, we don't want\n // to do all the \"creation\" options of the actions\n if (!this.props.state) {\n await Promise.all([this._setupPreProcessingCallbacks(), loadSpreadsheetDependencies()]);\n }\n const [record] = await Promise.all([this._fetchData(), loadSpreadsheetDependencies()]);\n this._initializeWith(record);\n }\n\n async _setupPreProcessingCallbacks() {\n if (this.params.preProcessingAction) {\n const initCallbackGenerator = initCallbackRegistry\n .get(this.params.preProcessingAction)\n .bind(this);\n this.initCallback = await initCallbackGenerator(this.params.preProcessingActionData);\n }\n if (this.params.preProcessingAsyncAction) {\n const initCallbackGenerator = initCallbackRegistry\n .get(this.params.preProcessingAsyncAction)\n .bind(this);\n this.asyncInitCallback = await initCallbackGenerator(\n this.params.preProcessingAsyncActionData\n );\n }\n }\n\n onMounted() {\n this.router.pushState({ spreadsheet_id: this.resId });\n this.env.config.setDisplayName(this.state.spreadsheetName);\n }\n\n /**\n * @protected\n * @abstract\n * @param {SpreadsheetRecord} record\n */\n _initializeWith(record) {\n throw new Error(\"not implemented by children\");\n }\n\n async _onMakeCopy() {\n throw new Error(\"not implemented by children\");\n }\n async _onNewSpreadsheet() {\n throw new Error(\"not implemented by children\");\n }\n async _onSpreadsheetSaved() {\n throw new Error(\"not implemented by children\");\n }\n async _onSpreadSheetNameChanged() {\n throw new Error(\"not implemented by children\");\n }\n\n /**\n * @returns {Promise}\n */\n async _fetchData() {\n throw new Error(\"not implemented by children\");\n }\n\n /**\n * @protected\n */\n _notifyCreation() {\n this.notifications.add(this.notificationMessage, {\n type: \"info\",\n sticky: false,\n });\n }\n\n /**\n * Open a spreadsheet\n * @private\n */\n _openSpreadsheet(spreadsheetId) {\n this._notifyCreation();\n this.actionService.doAction(\n {\n type: \"ir.actions.client\",\n tag: this.props.action.tag,\n params: { spreadsheet_id: spreadsheetId },\n },\n { clear_breadcrumbs: true }\n );\n }\n}\n", "/** @odoo-module **/\n\nimport { ControlPanel } from \"@web/search/control_panel/control_panel\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { SpreadsheetName } from \"./spreadsheet_name\";\n\nconst { Component, useState } = owl;\n\nexport class SpreadsheetControlPanel extends Component {\n setup() {\n this.controlPanelDisplay = {\n \"bottom-left\": false,\n \"bottom-right\": false,\n };\n this.actionService = useService(\"action\");\n this.breadcrumbs = useState(this.env.config.breadcrumbs);\n }\n\n /**\n * Called when an element of the breadcrumbs is clicked.\n *\n * @param {string} jsId\n */\n onBreadcrumbClicked(jsId) {\n this.actionService.restore(jsId);\n }\n}\n\nSpreadsheetControlPanel.template = \"spreadsheet_edition.SpreadsheetControlPanel\";\nSpreadsheetControlPanel.components = {\n ControlPanel,\n SpreadsheetName,\n};\nSpreadsheetControlPanel.props = {\n spreadsheetName: String,\n isSpreadsheetSynced: {\n type: Boolean,\n optional: true,\n },\n numberOfConnectedUsers: {\n type: Number,\n optional: true,\n },\n isReadonly: {\n type: Boolean,\n optional: true,\n },\n onSpreadsheetNameChanged: {\n type: Function,\n optional: true,\n },\n};\n", "/** @odoo-module **/\n\nimport { UNTITLED_SPREADSHEET_NAME } from \"@spreadsheet/helpers/constants\";\n\nconst { Component, onMounted, useState, useRef, onWillUpdateProps } = owl;\n\nconst WIDTH_MARGIN = 3;\nconst PADDING_RIGHT = 5;\nconst PADDING_LEFT = PADDING_RIGHT - WIDTH_MARGIN;\n\nexport class SpreadsheetName extends Component {\n setup() {\n this.placeholder = UNTITLED_SPREADSHEET_NAME;\n this.state = useState({\n inputSize: 1,\n isUntitled: this._isUntitled(this.props.name),\n name: this.props.name,\n });\n this.input = useRef(\"speadsheetNameInput\");\n\n onMounted(() => {\n this._setInputSize(this.state.name);\n });\n onWillUpdateProps(nextProps => {\n if (nextProps.name !== this.props.name) {\n this.state.name = nextProps.name;\n this.state.isUntitled = this._isUntitled(nextProps.name);\n }\n });\n }\n\n /**\n * @private\n * @param {string} text in the input element\n */\n _setInputSize(text) {\n const { fontFamily, fontSize } = window.getComputedStyle(this.input.el);\n const font = `${fontSize} ${fontFamily}`;\n this.state.inputSize =\n this._computeTextWidth(text || this.placeholder, font) +\n PADDING_RIGHT +\n PADDING_LEFT;\n }\n\n /**\n * Return the width in pixels of a text with the given font.\n * @private\n * @param {string} text\n * @param {string} font css font attribute value\n * @returns {number} width in pixels\n */\n _computeTextWidth(text, font) {\n const canvas = document.createElement(\"canvas\");\n const context = canvas.getContext(\"2d\");\n context.font = font;\n const width = context.measureText(text).width;\n // add a small extra margin, otherwise the text jitters in\n // the input because it overflows very slightly for some\n // letters (?).\n return Math.ceil(width) + WIDTH_MARGIN;\n }\n\n /**\n * Check if the name is empty or is the generic name\n * for untitled spreadsheets.\n * @param {string} name\n * @returns {boolean}\n */\n _isUntitled(name) {\n name = name.trim();\n return !name || name === UNTITLED_SPREADSHEET_NAME.toString();\n }\n\n /**\n * @private\n * @param {InputEvent} ev\n */\n _onFocus(ev) {\n if (this._isUntitled(ev.target.value)) {\n ev.target.value = this.placeholder;\n ev.target.select();\n }\n }\n\n /**\n * @private\n * @param {InputEvent} ev\n */\n _onInput(ev) {\n const value = ev.target.value;\n this.state.isUntitled = this._isUntitled(value);\n this.state.name = value\n this._setInputSize(value);\n }\n\n /**\n * @private\n * @param {InputEvent} ev\n */\n _onNameChanged(ev) {\n const value = ev.target.value.trim();\n ev.target.value = value;\n this._setInputSize(value);\n this.props.onSpreadsheetNameChanged({\n name: value,\n });\n ev.target.blur();\n }\n}\n\nSpreadsheetName.template = \"spreadsheet_edition.SpreadsheetName\";\nSpreadsheetName.props = {\n name: String,\n isReadonly: Boolean,\n onSpreadsheetNameChanged: { type: Function, optional: true },\n};\nSpreadsheetName.defaultProps = {\n onSpreadsheetNameChanged: () => {},\n};\n", "/** @odoo-module */\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport Dialog from \"web.OwlDialog\";\nimport { useSetupAction } from \"@web/webclient/actions/action_hook\";\nimport { useService } from \"@web/core/utils/hooks\";\n\nimport { DEFAULT_LINES_NUMBER } from \"@spreadsheet/helpers/constants\";\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { LegacyComponent } from \"@web/legacy/legacy_component\";\nimport { DataSources } from \"@spreadsheet/data_sources/data_sources\";\nimport { migrate } from \"@spreadsheet/o_spreadsheet/migration\";\n\nconst { onMounted, onWillUnmount, useExternalListener, useState, useSubEnv, onWillStart } = owl;\nconst uuidGenerator = new spreadsheet.helpers.UuidGenerator();\n\nconst { Spreadsheet, Model } = spreadsheet;\n\nconst tags = new Set();\n\nexport default class SpreadsheetComponent extends LegacyComponent {\n setup() {\n this.orm = useService(\"orm\");\n const user = useService(\"user\");\n this.ui = useService(\"ui\");\n this.action = useService(\"action\");\n this.notifications = useService(\"notification\");\n\n useSubEnv({\n newSpreadsheet: this.newSpreadsheet.bind(this),\n makeCopy: this.makeCopy.bind(this),\n download: this._download.bind(this),\n getLinesNumber: this._getLinesNumber.bind(this),\n notifyUser: this.notifyUser.bind(this),\n raiseError: this.raiseError.bind(this),\n editText: this.editText.bind(this),\n askConfirmation: this.askConfirmation.bind(this),\n });\n\n useSetupAction({\n beforeLeave: this._onLeave.bind(this),\n });\n\n useExternalListener(window, \"beforeunload\", this._onLeave.bind(this));\n\n this.state = useState({\n dialog: {\n isDisplayed: false,\n title: undefined,\n isEditText: false,\n errorText: undefined,\n inputContent: undefined,\n isEditInteger: false,\n inputIntegerContent: undefined,\n },\n });\n\n const dataSources = new DataSources(this.orm);\n\n this.model = new Model(\n migrate(this.props.data),\n {\n custom: { env: this.env, orm: this.orm, dataSources },\n external: {\n fileStore: this.props.fileStore,\n loadCurrencies: this.loadCurrencies.bind(this),\n },\n transportService: this.props.transportService,\n client: {\n id: uuidGenerator.uuidv4(),\n name: user.name,\n userId: user.userId,\n },\n mode: this.props.isReadonly ? \"readonly\" : \"normal\",\n snapshotRequested: this.props.snapshotRequested,\n },\n this.props.stateUpdateMessages\n );\n\n if (this.env.debug) {\n spreadsheet.__DEBUG__ = spreadsheet.__DEBUG__ || {};\n spreadsheet.__DEBUG__.model = this.model;\n }\n\n this.model.on(\"unexpected-revision-id\", this, () => {\n if (this.props.onUnexpectedRevisionId) {\n this.props.onUnexpectedRevisionId();\n }\n });\n dataSources.addEventListener(\"data-source-updated\", () => {\n const sheetId = this.model.getters.getActiveSheetId();\n this.model.dispatch(\"EVALUATE_CELLS\", { sheetId });\n });\n if (this.props.showFormulas) {\n this.model.dispatch(\"SET_FORMULA_VISIBILITY\", { show: true });\n }\n\n this.dialogContent = undefined;\n this.pivot = undefined;\n this.confirmDialog = () => true;\n\n onWillStart(async () => {\n if (this.props.asyncInitCallback) {\n await this.props.asyncInitCallback(this.model);\n }\n });\n\n onMounted(() => {\n if (this.props.initCallback) {\n this.props.initCallback(this.model);\n }\n this.model.on(\"update\", this, () => {\n if (this.props.spreadsheetSyncStatus) {\n this.props.spreadsheetSyncStatus({\n synced: this.model.getters.isFullySynchronized(),\n numberOfConnectedUsers: this.getConnectedUsers(),\n });\n }\n });\n });\n\n onWillUnmount(() => this._onLeave());\n }\n\n /**\n * Return the number of connected users. If one user has more than\n * one open tab, it's only counted once.\n * @return {number}\n */\n getConnectedUsers() {\n return new Set(\n [...this.model.getters.getConnectedClients().values()].map((client) => client.userId)\n ).size;\n }\n\n /**\n * Open a dialog to ask a confirmation to the user.\n *\n * @param {string} content Content to display\n * @param {Function} confirm Callback if the user press 'Confirm'\n */\n askConfirmation(content, confirm) {\n this.dialogContent = content;\n this.confirmDialog = () => {\n confirm();\n this.closeDialog();\n };\n this.state.dialog.isDisplayed = true;\n }\n\n /**\n * Ask the user to edit a text\n *\n * @param {string} title Title of the popup\n * @param {Function} callback Callback to call with the entered text\n * @param {Object} options Options of the dialog. Can contain a placeholder and an error message.\n */\n editText(title, callback, options = {}) {\n this.dialogContent = undefined;\n this.state.dialog.title = title && title.toString();\n this.state.dialog.errorText = options.error && options.error.toString();\n this.state.dialog.isEditText = true;\n this.state.inputContent = options.placeholder;\n this.confirmDialog = () => {\n this.closeDialog();\n callback(this.state.inputContent);\n };\n this.state.dialog.isDisplayed = true;\n }\n\n _getLinesNumber(callback) {\n this.dialogContent = _t(\"Select the number of records to insert\");\n this.state.dialog.title = _t(\"Re-insert list\");\n this.state.dialog.isEditInteger = true;\n this.state.dialog.inputIntegerContent = DEFAULT_LINES_NUMBER;\n this.confirmDialog = () => {\n this.closeDialog();\n callback(this.state.dialog.inputIntegerContent);\n };\n this.state.dialog.isDisplayed = true;\n }\n\n /**\n * Close the dialog.\n */\n closeDialog() {\n this.dialogContent = undefined;\n this.confirmDialog = () => true;\n this.state.dialog.title = undefined;\n this.state.dialog.errorText = undefined;\n this.state.dialog.isDisplayed = false;\n this.state.dialog.isEditText = false;\n this.state.dialog.isEditInteger = false;\n document.querySelector(\".o-grid>input\").focus();\n }\n\n /**\n * Load currencies from database\n */\n async loadCurrencies() {\n const odooCurrencies = await this.orm.searchRead(\n \"res.currency\", // model\n [], // domain\n [\"symbol\", \"full_name\", \"position\", \"name\", \"decimal_places\"], // fields\n {\n // opts\n order: \"active DESC, full_name ASC\",\n context: { active_test: false },\n }\n );\n return odooCurrencies.map((currency) => {\n return {\n code: currency.name,\n symbol: currency.symbol,\n position: currency.position || \"after\",\n name: currency.full_name || _t(\"Currency\"),\n decimalPlaces: currency.decimal_places || 2,\n };\n });\n }\n\n /**\n * Retrieve the spreadsheet_data and the thumbnail associated to the\n * current spreadsheet\n */\n getSaveData() {\n const data = this.model.exportData();\n return {\n data,\n revisionId: data.revisionId,\n thumbnail: this.getThumbnail(),\n };\n }\n\n getThumbnail() {\n const dimensions = spreadsheet.SPREADSHEET_DIMENSIONS;\n const canvas = document.querySelector(\".o-grid canvas:not(.o-figure-canvas)\");\n const canvasResizer = document.createElement(\"canvas\");\n const size = this.props.thumbnailSize;\n canvasResizer.width = size;\n canvasResizer.height = size;\n const canvasCtx = canvasResizer.getContext(\"2d\");\n // use only 25 first rows in thumbnail\n const sourceSize = Math.min(\n 25 * dimensions.DEFAULT_CELL_HEIGHT,\n canvas.width,\n canvas.height\n );\n canvasCtx.drawImage(\n canvas,\n dimensions.HEADER_WIDTH - 1,\n dimensions.HEADER_HEIGHT - 1,\n sourceSize,\n sourceSize,\n 0,\n 0,\n size,\n size\n );\n return canvasResizer.toDataURL().replace(\"data:image/png;base64,\", \"\");\n }\n /**\n * Make a copy of the current document\n */\n makeCopy() {\n const { data, thumbnail } = this.getSaveData();\n this.props.onMakeCopy({ data, thumbnail });\n }\n /**\n * Create a new spreadsheet\n */\n newSpreadsheet() {\n this.props.onNewSpreadsheet();\n }\n\n /**\n * Downloads the spreadsheet in xlsx format\n */\n async _download() {\n this.ui.block();\n try {\n await this.action.doAction({\n type: \"ir.actions.client\",\n tag: \"action_download_spreadsheet\",\n params: {\n orm: this.orm,\n name: this.props.name,\n data: this.model.exportData(),\n stateUpdateMessages: [],\n },\n });\n } finally {\n this.ui.unblock();\n }\n }\n\n /**\n * Adds a notification to display to the user\n * @param {{text: string, tag: string}} notification\n */\n notifyUser(notification) {\n if (tags.has(notification.tag)) {\n return;\n }\n this.notifications.add(notification.text, {\n type: \"warning\",\n sticky: true,\n onClose: () => tags.delete(notification.tag),\n });\n tags.add(notification.tag);\n }\n\n /**\n * Open a dialog to display an error message to the user.\n *\n * @param {string} content Content to display\n */\n raiseError(content) {\n this.dialogContent = content;\n this.confirmDialog = this.closeDialog;\n this.state.dialog.isDisplayed = true;\n }\n\n _onLeave() {\n if (this.alreadyLeft) {\n return;\n }\n this.alreadyLeft = true;\n this.model.leaveSession();\n this.model.off(\"update\", this);\n if (!this.props.isReadonly) {\n this.props.onSpreadsheetSaved(this.getSaveData());\n }\n }\n}\n\nSpreadsheetComponent.template = \"spreadsheet_edition.SpreadsheetComponent\";\nSpreadsheetComponent.components = { Spreadsheet, Dialog };\nSpreadsheet._t = _t;\nSpreadsheetComponent.props = {\n name: String,\n data: Object,\n thumbnailSize: Number,\n isReadonly: { type: Boolean, optional: true },\n snapshotRequested: { type: Boolean, optional: true },\n showFormulas: { type: Boolean, optional: true },\n stateUpdateMessages: { type: Array, optional: true },\n asyncInitCallback: {\n optional: true,\n type: Function,\n },\n initCallback: {\n optional: true,\n type: Function,\n },\n transportService: {\n optional: true,\n type: Object,\n },\n fileStore: {\n optional: true,\n type: Object,\n },\n spreadsheetSyncStatus: {\n optional: true,\n type: Function,\n },\n onDownload: {\n optional: true,\n type: Function,\n },\n onUnexpectedRevisionId: {\n optional: true,\n type: Function,\n },\n onMakeCopy: {\n type: Function,\n },\n onSpreadsheetSaved: {\n type: Function,\n },\n onNewSpreadsheet: {\n type: Function,\n },\n};\nSpreadsheetComponent.defaultProps = {\n isReadonly: false,\n snapshotRequested: false,\n showFormulas: false,\n stateUpdateMessages: [],\n onDownload: () => {},\n};\n", "/** @odoo-module **/\n\nimport spreadsheet, {\n initCallbackRegistry,\n} from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nconst uuidGenerator = new spreadsheet.helpers.UuidGenerator();\n\nexport function insertChart(chartData) {\n const definition = {\n metaData: {\n groupBy: chartData.metaData.groupBy,\n measure: chartData.metaData.measure,\n order: chartData.metaData.order,\n resModel: chartData.metaData.resModel,\n },\n searchParams: { ...chartData.searchParams },\n stacked: chartData.metaData.stacked,\n title: chartData.name,\n background: \"#FFFFFF\",\n legendPosition: \"top\",\n verticalAxisPosition: \"left\",\n type: `odoo_${chartData.metaData.mode}`,\n dataSourceId: uuidGenerator.uuidv4(),\n id: uuidGenerator.uuidv4(),\n };\n return (model) => {\n model.dispatch(\"CREATE_CHART\", {\n sheetId: model.getters.getActiveSheetId(),\n id: definition.id,\n position: {\n x: 10,\n y: 10,\n },\n definition,\n });\n if (chartData.menuXMLId) {\n model.dispatch(\"LINK_ODOO_MENU_TO_CHART\", {\n chartId: definition.id,\n odooMenuId: chartData.menuXMLId,\n });\n }\n };\n}\n\ninitCallbackRegistry.add(\"insertChart\", insertChart);\n", "/** @odoo-module **/\n\nimport { patch } from \"@web/core/utils/patch\";\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { IrMenuSelector } from \"@spreadsheet_edition/assets/components/ir_menu_selector/ir_menu_selector\";\n\nconst { LineBarPieConfigPanel, ScorecardChartConfigPanel, GaugeChartConfigPanel } =\n spreadsheet.components;\n\n/**\n * Patch the chart configuration panel to add an input to\n * link the chart to an Odoo menu.\n */\nfunction patchChartPanelWithMenu(PanelComponent, patchName) {\n patch(PanelComponent.prototype, patchName, {\n get odooMenuId() {\n const menu = this.env.model.getters.getChartOdooMenu(this.props.figureId);\n return menu ? menu.id : undefined;\n },\n /**\n * @param {number | undefined} odooMenuId\n */\n updateOdooLink(odooMenuId) {\n if (!odooMenuId) {\n this.env.model.dispatch(\"LINK_ODOO_MENU_TO_CHART\", {\n chartId: this.props.figureId,\n odooMenuId: undefined,\n });\n return;\n }\n const menu = this.env.model.getters.getIrMenu(odooMenuId);\n this.env.model.dispatch(\"LINK_ODOO_MENU_TO_CHART\", {\n chartId: this.props.figureId,\n odooMenuId: menu.xmlid || menu.id,\n });\n },\n });\n PanelComponent.components = {\n ...PanelComponent.components,\n IrMenuSelector,\n };\n}\npatchChartPanelWithMenu(LineBarPieConfigPanel, \"document_spreadsheet.LineBarPieConfigPanel\");\npatchChartPanelWithMenu(GaugeChartConfigPanel, \"document_spreadsheet.GaugeChartConfigPanel\");\npatchChartPanelWithMenu(\n ScorecardChartConfigPanel,\n \"document_spreadsheet.ScorecardChartConfigPanel\"\n);\n", "/** @odoo-module */\n\nimport { IrMenuSelector } from \"@spreadsheet_edition/assets/components/ir_menu_selector/ir_menu_selector\";\n\nconst { Component } = owl;\n\nexport class CommonOdooChartConfigPanel extends Component {\n get odooMenuId() {\n const menu = this.env.model.getters.getChartOdooMenu(this.props.figureId);\n return menu ? menu.id : undefined;\n }\n /**\n * @param {number | undefined} odooMenuId\n */\n updateOdooLink(odooMenuId) {\n if (!odooMenuId) {\n this.env.model.dispatch(\"LINK_ODOO_MENU_TO_CHART\", {\n chartId: this.props.figureId,\n odooMenuId: undefined,\n });\n return;\n }\n const menu = this.env.model.getters.getIrMenu(odooMenuId);\n this.env.model.dispatch(\"LINK_ODOO_MENU_TO_CHART\", {\n chartId: this.props.figureId,\n odooMenuId: menu.xmlid || menu.id,\n });\n }\n}\n\nCommonOdooChartConfigPanel.template = \"spreadsheet_edition.CommonOdooChartConfigPanel\";\nCommonOdooChartConfigPanel.components = { IrMenuSelector };\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { CommonOdooChartConfigPanel } from \"./common/config_panel\";\nimport { OdooBarChartConfigPanel } from \"./odoo_bar/odoo_bar_config_panel\";\nimport { OdooLineChartConfigPanel } from \"./odoo_line/odoo_line_config_panel\";\n\nconst { chartSidePanelComponentRegistry } = spreadsheet.registries;\nconst { LineBarPieDesignPanel } = spreadsheet.components;\n\nchartSidePanelComponentRegistry\n .add(\"odoo_line\", {\n configuration: OdooLineChartConfigPanel,\n design: LineBarPieDesignPanel,\n })\n .add(\"odoo_bar\", {\n configuration: OdooBarChartConfigPanel,\n design: LineBarPieDesignPanel,\n })\n .add(\"odoo_pie\", {\n configuration: CommonOdooChartConfigPanel,\n design: LineBarPieDesignPanel,\n });\n", "/** @odoo-module **/\n\nimport { patch } from \"@web/core/utils/patch\";\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nconst { chartRegistry } = spreadsheet.registries;\nconst { ChartPanel } = spreadsheet.components;\n\n/**\n * This patch is necessary to ensure that the chart type cannot be changed\n * between odoo charts and spreadsheet charts.\n */\n\npatch(ChartPanel.prototype, \"spreadsheet.ChartPanel\", {\n get chartTypes() {\n const definition = this.getChartDefinition();\n const isOdoo = definition.type.startsWith(\"odoo_\");\n return this.getChartTypes(isOdoo);\n },\n\n /**\n * @param {boolean} isOdoo\n */\n getChartTypes(isOdoo) {\n const result = {};\n for (const key of chartRegistry.getKeys()) {\n if ((isOdoo && key.startsWith(\"odoo_\")) || (!isOdoo && !key.startsWith(\"odoo_\"))) {\n result[key] = chartRegistry.get(key).name;\n }\n }\n return result;\n },\n\n onTypeChange(type) {\n if (this.getChartDefinition().type.startsWith(\"odoo_\")) {\n const definition = {\n stacked: false,\n verticalAxisPosition: \"left\",\n ...this.env.model.getters.getChartDefinition(this.figureId),\n type,\n };\n this.env.model.dispatch(\"UPDATE_CHART\", {\n definition,\n id: this.figureId,\n sheetId: this.env.model.getters.getActiveSheetId(),\n });\n } else {\n this._super(type);\n }\n },\n});\n", "/** @odoo-module */\n\nimport { IrMenuSelector } from \"@spreadsheet_edition/assets/components/ir_menu_selector/ir_menu_selector\";\nimport { CommonOdooChartConfigPanel } from \"../common/config_panel\";\n\nexport class OdooBarChartConfigPanel extends CommonOdooChartConfigPanel {\n onUpdateStacked(ev) {\n this.props.updateChart({\n stacked: ev.target.checked,\n });\n }\n}\n\nOdooBarChartConfigPanel.template = \"spreadsheet_edition.OdooBarChartConfigPanel\";\nOdooBarChartConfigPanel.components = { IrMenuSelector };\n", "/** @odoo-module */\n\nimport { IrMenuSelector } from \"@spreadsheet_edition/assets/components/ir_menu_selector/ir_menu_selector\";\nimport { CommonOdooChartConfigPanel } from \"../common/config_panel\";\n\nexport class OdooLineChartConfigPanel extends CommonOdooChartConfigPanel {\n onUpdateStacked(ev) {\n this.props.updateChart({\n stacked: ev.target.checked,\n });\n }\n}\n\nOdooLineChartConfigPanel.template = \"spreadsheet_edition.OdooLineChartConfigPanel\";\nOdooLineChartConfigPanel.components = { IrMenuSelector };\n", "/** @odoo-module */\n\nimport { _lt } from \"@web/core/l10n/translation\";\nimport { FilterFieldOffset } from \"../filter_field_offset\";\nimport { RELATIVE_DATE_RANGE_TYPES } from \"@spreadsheet/helpers/constants\";\nimport AbstractFilterEditorSidePanel from \"./filter_editor_side_panel\";\nimport FilterEditorFieldMatching from \"./filter_editor_field_matching\";\nimport FilterEditorLabel from \"./filter_editor_label\";\n\nconst { useState } = owl;\n\nconst RANGE_TYPES = [\n { type: \"year\", description: _lt(\"Year\") },\n { type: \"quarter\", description: _lt(\"Quarter\") },\n { type: \"month\", description: _lt(\"Month\") },\n { type: \"relative\", description: _lt(\"Relative Period\") },\n];\n\n/**\n * @typedef {import(\"@spreadsheet/global_filters/plugins/global_filters_core_plugin\").GlobalFilter} GlobalFilter\n * @typedef {import(\"@spreadsheet/data_sources/metadata_repository\").Field} Field\n * @typedef {import(\"@spreadsheet/global_filters/plugins/global_filters_core_plugin\").FieldMatching} FieldMatching\n *\n * @typedef DateState\n * @property {Object} defaultValue\n * @property {boolean} defaultsToCurrentPeriod\n * @property {boolean} automaticDefaultValue\n * @property {\"year\" | \"month\" | \"quarter\" | \"relative\"} type type of the filter\n */\n\nclass DateFilterEditorFieldMatching extends FilterEditorFieldMatching {}\n\nDateFilterEditorFieldMatching.components = {\n ...FilterEditorFieldMatching.components,\n FilterFieldOffset,\n};\n\nDateFilterEditorFieldMatching.template = \"spreadsheet_edition.DateFilterEditorFieldMatching\";\n\nDateFilterEditorFieldMatching.props = {\n ...FilterEditorFieldMatching.props,\n onOffsetSelected: Function,\n};\n\n/**\n * This is the side panel to define/edit a global filter of type \"date\".\n */\nexport default class DateFilterEditorSidePanel extends AbstractFilterEditorSidePanel {\n /**\n * @constructor\n */\n setup() {\n super.setup();\n\n this.type = \"date\";\n /** @type {DateState} */\n this.dateState = useState({\n defaultValue: {},\n defaultsToCurrentPeriod: false,\n automaticDefaultValue: false,\n type: \"year\",\n options: [],\n });\n\n this.relativeDateRangesTypes = RELATIVE_DATE_RANGE_TYPES;\n this.dateRangeTypes = RANGE_TYPES;\n\n this.ALLOWED_FIELD_TYPES = [\"datetime\", \"date\"];\n }\n\n /**\n * @override\n */\n get filterValues() {\n const values = super.filterValues;\n return {\n ...values,\n defaultValue: this.dateState.defaultValue,\n rangeType: this.dateState.type,\n defaultsToCurrentPeriod: this.dateState.defaultsToCurrentPeriod,\n };\n }\n\n shouldDisplayFieldMatching() {\n return this.fieldMatchings.length;\n }\n\n isDateTypeSelected(dateType) {\n return dateType === this.dateState.type;\n }\n\n /**\n * @override\n * @param {GlobalFilter} globalFilter\n */\n loadSpecificFilterValues(globalFilter) {\n this.dateState.type = globalFilter.rangeType;\n this.dateState.defaultValue = globalFilter.defaultValue;\n this.dateState.automaticDefaultValue = globalFilter.automaticDefaultValue;\n this.dateState.defaultsToCurrentPeriod = globalFilter.defaultsToCurrentPeriod;\n }\n\n /**\n * @override\n * @param {string} index\n * @param {string|undefined} chain\n * @param {Field|undefined} field\n */\n onSelectedField(index, chain, field) {\n super.onSelectedField(index, chain, field);\n this.fieldMatchings[index].fieldMatch.offset = 0;\n }\n\n /**\n * @param {string} index\n * @param {number} offset\n */\n onOffsetSelected(index, offset) {\n this.fieldMatchings[index].fieldMatch.offset = offset;\n }\n\n onTimeRangeChanged(defaultValue) {\n this.dateState.defaultValue = defaultValue;\n }\n\n onDateOptionChange(ev) {\n // TODO t-model does not work ?\n this.dateState.type = ev.target.value;\n this.dateState.defaultValue = this.dateState.type !== \"relative\" ? {} : \"\";\n }\n\n toggleDefaultsToCurrentPeriod(ev) {\n this.dateState.defaultsToCurrentPeriod = ev.target.checked;\n }\n}\n\nDateFilterEditorSidePanel.template = \"spreadsheet_edition.DateFilterEditorSidePanel\";\nDateFilterEditorSidePanel.components = {\n DateFilterEditorFieldMatching,\n FilterEditorLabel,\n};\n", "/** @odoo-module */\n\nimport { SpreadsheetModelFieldSelector } from \"../model_field_selector/spreadsheet_model_field_selector\";\n\nconst { Component } = owl;\n\n/**\n * @typedef {import(\"@spreadsheet/global_filters/plugins/global_filters_core_plugin\").FieldMatching} FieldMatching\n * @typedef {import(\"@spreadsheet/data_sources/metadata_repository\").Field} Field\n */\n\nexport default class FilterEditorFieldMatching extends Component {\n /**\n *\n * @param {FieldMatching} fieldMatch\n * @returns {string}\n */\n getModelField(fieldMatch) {\n if (!fieldMatch || !fieldMatch.chain) {\n return \"\";\n }\n return fieldMatch.chain;\n }\n\n /**\n * @param {{resModel:string, field: Field}[]} [fieldChain]\n * @return {Field | undefined}\n */\n extractField(fieldChain) {\n if (!fieldChain) {\n return undefined;\n }\n const candidate = [...fieldChain].reverse().find((chain) => chain.field);\n return candidate ? candidate.field : undefined;\n }\n}\nFilterEditorFieldMatching.template = \"spreadsheet_edition.FilterEditorFieldMatching\";\n\nFilterEditorFieldMatching.components = {\n SpreadsheetModelFieldSelector,\n};\n\nFilterEditorFieldMatching.props = {\n // See AbstractFilterEditorSidePanel fieldMatchings\n fieldMatchings: Array,\n wrongFieldMatchings: Array,\n selectField: Function,\n filterModelFieldSelectorField: Function,\n};\n", "/** @odoo-module */\n\nconst { Component, onMounted, useRef } = owl;\n\nexport default class FilterEditorLabel extends Component {\n setup() {\n this.labelInput = useRef(\"labelInput\");\n onMounted(this.onMounted);\n }\n\n onMounted() {\n this.labelInput.el.focus();\n }\n}\nFilterEditorLabel.template = \"spreadsheet_edition.FilterEditorLabel\";\n\nFilterEditorLabel.props = {\n label: { type: String, optional: true },\n placeholder: { type: String, optional: true },\n inputClass: { type: String, optional: true },\n setLabel: Function,\n};\n", "/** @odoo-module */\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport CommandResult from \"@spreadsheet/o_spreadsheet/cancelled_reason\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { sprintf } from \"@web/core/utils/strings\";\nimport { globalFiltersFieldMatchers } from \"@spreadsheet/global_filters/plugins/global_filters_core_plugin\";\n\nconst { onWillStart, Component, useRef, useState, toRaw } = owl;\nconst uuidGenerator = new spreadsheet.helpers.UuidGenerator();\n\n/**\n * @typedef {import(\"@spreadsheet/data_sources/metadata_repository\").Field} Field\n * @typedef {import(\"@spreadsheet/global_filters/plugins/global_filters_core_plugin\").FieldMatching} FieldMatching\n * @typedef {import(\"@spreadsheet/global_filters/plugins/global_filters_core_plugin\").GlobalFilter} GlobalFilter\n *\n * @typedef State\n * @property {boolean} saved\n * @property {string} label label of the filter\n */\n\n/**\n * This is the side panel to define/edit a global filter.\n * It can be of 3 different type: text, date and relation.\n */\nexport default class AbstractFilterEditorSidePanel extends Component {\n setup() {\n this.id = undefined;\n this.type = \"\";\n /** @type {State} */\n this.genericState = useState({\n saved: false,\n label: undefined,\n });\n this.fieldMatchings = useState([]);\n this._wrongFieldMatchingsSet = new Set();\n this.getters = this.env.model.getters;\n this.orm = useService(\"orm\");\n this.notification = useService(\"notification\");\n this.labelInput = useRef(\"labelInput\");\n\n /** @type {string[]} */\n this.ALLOWED_FIELD_TYPES = [];\n\n onWillStart(this.onWillStart);\n }\n\n /**\n * Retrieve the placeholder of the label\n */\n get placeholder() {\n return sprintf(_t(\"New %s filter\"), this.type);\n }\n\n get missingLabel() {\n return this.genericState.saved && !this.genericState.label;\n }\n\n get wrongFieldMatchings() {\n return this.genericState.saved ? [...this._wrongFieldMatchingsSet] : [];\n }\n\n get missingRequired() {\n return !!this.missingLabel || this.wrongFieldMatchings.length !== 0;\n }\n\n get filterValues() {\n const id = this.id || uuidGenerator.uuidv4();\n return {\n id,\n type: this.type,\n label: this.genericState.label,\n };\n }\n\n /**\n * @param {Event & { target: HTMLInputElement }} ev\n */\n setLabel(ev) {\n this.genericState.label = ev.target.value;\n }\n\n shouldDisplayFieldMatching() {\n throw new Error(\"Not implemented by children\");\n }\n\n loadValues() {\n this.id = this.props.id;\n const globalFilter = this.id && this.getters.getGlobalFilter(this.id);\n if (globalFilter) {\n this.genericState.label = _t(globalFilter.label);\n this.loadSpecificFilterValues(globalFilter);\n }\n }\n\n /**\n * @param {GlobalFilter} globalFilter\n */\n loadSpecificFilterValues(globalFilter) {\n return;\n }\n\n /**\n * @private\n */\n async _loadFieldMatchings() {\n for (const [type, el] of Object.entries(globalFiltersFieldMatchers)) {\n for (const objectId of el.geIds()) {\n const tag = await el.getTag(objectId);\n this.fieldMatchings.push({\n name: el.getDisplayName(objectId),\n tag,\n fieldMatch: el.getFieldMatching(objectId, this.id) || {},\n fields: () => el.getFields(objectId),\n model: () => el.getModel(objectId),\n payload: () => ({ id: objectId, type }),\n });\n }\n }\n }\n\n async onWillStart() {\n this.loadValues();\n const proms = [];\n proms.push(\n ...Object.values(globalFiltersFieldMatchers)\n .map((el) => el.waitForReady())\n .flat()\n );\n await this._loadFieldMatchings();\n await Promise.all(proms);\n }\n\n /**\n * @param {Field} field\n * @returns {boolean}\n */\n isFieldValid(field) {\n return !!field.searchable;\n }\n\n /**\n * Function that will be called by ModelFieldSelector on each fields, to\n * filter the ones that should be displayed\n * @param {Field} field\n * @returns {boolean}\n */\n filterModelFieldSelectorField(field) {\n if (this.env.debug) {\n // Debug users are allowed to go through relational fields a target multi-depth\n // relations e.g. product_id.categ_id.name\n return this.ALLOWED_FIELD_TYPES.includes(field.type) || !!field.relation;\n }\n if (this.ALLOWED_FIELD_TYPES.includes(field.type)) {\n if (this.isFieldValid(field)) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * @param {{resModel:string, field: Object}[] | undefined} fieldChain\n * @return {Object | undefined}\n */\n extractField(fieldChain) {\n if (!fieldChain) {\n return undefined;\n }\n const candidate = [...fieldChain].reverse().find((chain) => chain.field);\n return candidate ? candidate.field : candidate;\n }\n\n /**\n *\n * @param {Object} field\n * @returns {boolean}\n */\n matchingRelation(field) {\n return !field.relation;\n }\n\n /**\n * @param {string} index\n * @param {string|undefined} chain\n * @param {Field|undefined} field\n */\n onSelectedField(index, chain, field) {\n if (!chain || !field) {\n this.fieldMatchings[index].fieldMatch = {};\n return;\n }\n const fieldName = chain;\n this.fieldMatchings[index].fieldMatch = {\n chain: fieldName,\n type: field.type,\n };\n if (!this.matchingRelation(field)) {\n this._wrongFieldMatchingsSet.add(index);\n } else {\n this._wrongFieldMatchingsSet.delete(index);\n }\n }\n\n /**\n *\n * @param {FieldMatching} fieldMatch\n * @returns\n */\n getModelField(fieldMatch) {\n if (!fieldMatch || !fieldMatch.chain) {\n return \"\";\n }\n return fieldMatch.chain;\n }\n\n onSave() {\n this.genericState.saved = true;\n if (this.missingRequired) {\n this.notification.add(this.env._t(\"Some required fields are not valid\"), {\n type: \"danger\",\n sticky: false,\n });\n return;\n }\n const cmd = this.id ? \"EDIT_GLOBAL_FILTER\" : \"ADD_GLOBAL_FILTER\";\n const filter = this.filterValues;\n // Populate the command a bit more with a key chart, pivot or list\n const additionalPayload = {};\n this.fieldMatchings.forEach((fm) => {\n const { type, id } = fm.payload();\n additionalPayload[type] = additionalPayload[type] || {};\n //remove reactivity\n additionalPayload[type][id] = toRaw(fm.fieldMatch);\n });\n const result = this.env.model.dispatch(cmd, {\n id: filter.id,\n filter,\n ...additionalPayload,\n });\n if (result.isCancelledBecause(CommandResult.DuplicatedFilterLabel)) {\n this.notification.add(this.env._t(\"Duplicated Label\"), {\n type: \"danger\",\n sticky: false,\n });\n return;\n }\n this.env.openSidePanel(\"GLOBAL_FILTERS_SIDE_PANEL\", {});\n }\n\n onCancel() {\n this.env.openSidePanel(\"GLOBAL_FILTERS_SIDE_PANEL\", {});\n }\n\n onDelete() {\n if (this.id) {\n this.env.model.dispatch(\"REMOVE_GLOBAL_FILTER\", { id: this.id });\n }\n this.env.openSidePanel(\"GLOBAL_FILTERS_SIDE_PANEL\", {});\n }\n}\n\nAbstractFilterEditorSidePanel.props = {\n id: { type: String, optional: true },\n onCloseSidePanel: { type: Function, optional: true },\n};\n", "/** @odoo-module */\n\nimport { RecordsSelector } from \"@spreadsheet/global_filters/components/records_selector/records_selector\";\nimport { ModelSelector } from \"@web/core/model_selector/model_selector\";\nimport AbstractFilterEditorSidePanel from \"./filter_editor_side_panel\";\nimport FilterEditorLabel from \"./filter_editor_label\";\nimport FilterEditorFieldMatching from \"./filter_editor_field_matching\";\n\nconst { useState } = owl;\n\n/**\n * @typedef {import(\"@spreadsheet/data_sources/metadata_repository\").Field} Field\n * @typedef {import(\"@spreadsheet/global_filters/plugins/global_filters_core_plugin\").GlobalFilter} GlobalFilter\n\n *\n * @typedef RelationState\n * @property {Array} defaultValue\n * @property {Array} displayNames\n * @property {{label?: string, technical?: string}} relatedModel\n */\n\n/**\n * This is the side panel to define/edit a global filter of type \"relation\".\n */\nexport default class RelationFilterEditorSidePanel extends AbstractFilterEditorSidePanel {\n setup() {\n super.setup();\n\n this.type = \"relation\";\n /** @type {RelationState} */\n this.relationState = useState({\n defaultValue: [],\n displayNames: [],\n relatedModel: {\n label: undefined,\n technical: undefined,\n },\n });\n\n this.ALLOWED_FIELD_TYPES = [\"many2one\", \"many2many\", \"one2many\"];\n }\n\n get missingModel() {\n return this.genericState.saved && !this.relationState.relatedModel.technical;\n }\n\n get missingRequired() {\n return super.missingRequired || this.missingModel;\n }\n\n /**\n * @override\n */\n get filterValues() {\n const values = super.filterValues;\n return {\n ...values,\n defaultValue: this.relationState.defaultValue,\n defaultValueDisplayNames: this.relationState.displayNames,\n modelName: this.relationState.relatedModel.technical,\n };\n }\n\n shouldDisplayFieldMatching() {\n return this.fieldMatchings.length && this.relationState.relatedModel.technical;\n }\n\n /**\n * List of model names of all related models of all pivots\n * @returns {Array}\n */\n get relatedModels() {\n const all = this.fieldMatchings.map((object) => Object.values(object.fields()));\n return [\n ...new Set(\n all\n .flat()\n .filter((field) => field.relation)\n .map((field) => field.relation)\n ),\n ];\n }\n\n /**\n * @override\n * @param {GlobalFilter} globalFilter\n */\n loadSpecificFilterValues(globalFilter) {\n this.relationState.defaultValue = globalFilter.defaultValue;\n this.relationState.relatedModel.technical = globalFilter.modelName;\n }\n\n async onWillStart() {\n await super.onWillStart();\n await this.fetchModelFromName();\n }\n\n /**\n * Get the first field which could be a relation of the current related\n * model\n *\n * @param {Object.} fields Fields to look in\n * @returns {field|undefined}\n */\n _findRelation(fields) {\n const field = Object.values(fields).find(\n (field) =>\n field.searchable && field.relation === this.relationState.relatedModel.technical\n );\n return field;\n }\n\n async onModelSelected({ technical, label }) {\n if (!this.genericState.label) {\n this.genericState.label = label;\n }\n if (this.relationState.relatedModel.technical !== technical) {\n this.relationState.defaultValue = [];\n }\n this.relationState.relatedModel.technical = technical;\n this.relationState.relatedModel.label = label;\n\n for (const [index, object] of Object.entries(this.fieldMatchings)) {\n const field = this._findRelation(object.fields());\n this.onSelectedField(index, field ? field.name : undefined, field);\n }\n }\n\n async fetchModelFromName() {\n if (!this.relationState.relatedModel.technical) {\n return;\n }\n const result = await this.orm.call(\"ir.model\", \"display_name_for\", [\n [this.relationState.relatedModel.technical],\n ]);\n this.relationState.relatedModel.label = result[0] && result[0].display_name;\n if (!this.genericState.label) {\n this.genericState.label = this.relationState.relatedModel.label;\n }\n }\n\n /**\n * @param {Field} field\n * @returns {boolean}\n */\n isFieldValid(field) {\n const relatedModel = this.relationState.relatedModel.technical;\n return super.isFieldValid(field) && (!relatedModel || field.relation === relatedModel);\n }\n\n /**\n * @override\n * @param {Field} field\n * @returns {boolean}\n */\n matchingRelation(field) {\n return field.relation === this.relationState.relatedModel.technical;\n }\n\n /**\n * @param {{id: number, display_name: string}[]} value\n */\n onValuesSelected(value) {\n this.relationState.defaultValue = value.map((record) => record.id);\n this.relationState.displayNames = value.map((record) => record.display_name);\n }\n}\n\nRelationFilterEditorSidePanel.template = \"spreadsheet_edition.RelationFilterEditorSidePanel\";\nRelationFilterEditorSidePanel.components = {\n ModelSelector,\n RecordsSelector,\n FilterEditorLabel,\n FilterEditorFieldMatching,\n};\n", "/** @odoo-module */\n\nimport AbstractFilterEditorSidePanel from \"./filter_editor_side_panel\";\nimport FilterEditorLabel from \"./filter_editor_label\";\nimport FilterEditorFieldMatching from \"./filter_editor_field_matching\";\n\nconst { useState } = owl;\n\n/**\n * @typedef {import(\"@spreadsheet/global_filters/plugins/global_filters_core_plugin\").GlobalFilter} GlobalFilter\n *\n * @typedef TextState\n * @property {string} defaultValue\n\n */\n\n/**\n * This is the side panel to define/edit a global filter of type \"text\".\n */\nexport default class TextFilterEditorSidePanel extends AbstractFilterEditorSidePanel {\n setup() {\n super.setup();\n\n this.type = \"text\";\n /** @type {TextState} */\n this.textState = useState({\n defaultValue: \"\",\n });\n this.ALLOWED_FIELD_TYPES = [\"many2one\", \"text\", \"char\"];\n }\n\n /**\n * @override\n */\n shouldDisplayFieldMatching() {\n return this.fieldMatchings.length;\n }\n\n /**\n * @override\n */\n get filterValues() {\n const values = super.filterValues;\n return {\n ...values,\n defaultValue: this.textState.defaultValue,\n };\n }\n\n /**\n * @override\n * @param {GlobalFilter} globalFilter\n */\n loadSpecificFilterValues(globalFilter) {\n this.textState.defaultValue = globalFilter.defaultValue;\n }\n}\n\nTextFilterEditorSidePanel.template = \"spreadsheet_edition.TextFilterEditorSidePanel\";\nTextFilterEditorSidePanel.components = {\n FilterEditorLabel,\n FilterEditorFieldMatching,\n};\n", "/** @odoo-module */\n\nconst { Component } = owl;\nimport { _t, _lt } from \"@web/core/l10n/translation\";\n\nconst FIELD_OFFSETS = [\n { value: 0, description: \"\" },\n { value: -1, description: _lt(\"Previous\") },\n { value: -2, description: _lt(\"Before previous\") },\n { value: 1, description: _lt(\"Next\") },\n { value: 2, description: _lt(\"After next\") },\n];\n\nexport class FilterFieldOffset extends Component {\n setup() {\n this.fieldsOffsets = FIELD_OFFSETS;\n }\n\n /**\n * @param {Event & { target: HTMLSelectElement }} ev\n */\n onOffsetSelected(ev) {\n this.props.onOffsetSelected(parseInt(ev.target.value));\n }\n\n get title() {\n return this.props.active\n ? _t(\"Period offset applied to this source\")\n : _t(\"Requires a selected field\");\n }\n}\n\nFilterFieldOffset.template = \"spreadsheet_edition.FilterFieldOffset\";\nFilterFieldOffset.props = {\n onOffsetSelected: Function,\n selectedOffset: Number,\n active: Boolean,\n};\n", "/** @odoo-module */\n\nimport { ModelFieldSelector } from \"@web/core/model_field_selector/model_field_selector\";\nimport { SpreadsheetModelFieldSelectorPopover } from \"./spreadsheet_model_field_selector_popover\";\n\n/**\n * @typedef {import(\"@spreadsheet/data_sources/metadata_repository\").Field} Field\n */\nexport class SpreadsheetModelFieldSelector extends ModelFieldSelector {\n /**\n * @override\n *\n * @param {string[]} fieldNameChain\n * @param {Object[]} chain\n */\n update(fieldNameChain, chain) {\n this.props.update(fieldNameChain.join(\".\"), chain);\n }\n}\nSpreadsheetModelFieldSelector.components = {\n Popover: SpreadsheetModelFieldSelectorPopover,\n};\n", "/** @odoo-module */\n\nimport { ModelFieldSelectorPopover } from \"@web/core/model_field_selector/model_field_selector_popover\";\n\nexport class SpreadsheetModelFieldSelectorPopover extends ModelFieldSelectorPopover {\n async update() {\n const fieldNameChain = this.fieldNameChain;\n this.fullFieldName = fieldNameChain.join(\".\");\n await this.props.update(fieldNameChain, [...this.chain]);\n await this.loadFields();\n this.render();\n }\n}\n", "/** @odoo-module */\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nconst { Component } = owl;\nconst { Menu } = spreadsheet;\n\nexport class FilterComponent extends Component {\n get activeFilter() {\n return this.env.model.getters.getActiveFilterCount();\n }\n\n toggleDropdown() {\n this.env.toggleSidePanel(\"GLOBAL_FILTERS_SIDE_PANEL\");\n }\n}\n\nFilterComponent.template = \"spreadsheet_edition.FilterComponent\";\n\nFilterComponent.components = { Menu };\n\nFilterComponent.props = {};\n", "/** @odoo-module */\n\nimport { FilterValue } from \"@spreadsheet/global_filters/components/filter_value/filter_value\";\n\nconst { Component } = owl;\n/**\n * This is the side panel to define/edit a global filter.\n * It can be of 3 different type: text, date and relation.\n */\nexport default class GlobalFiltersSidePanel extends Component {\n setup() {\n this.getters = this.env.model.getters;\n }\n\n get isReadonly() {\n return this.env.model.getters.isReadonly();\n }\n\n get filters() {\n return this.env.model.getters.getGlobalFilters();\n }\n\n hasDataSources() {\n return (\n this.env.model.getters.getPivotIds().length +\n this.env.model.getters.getListIds().length +\n this.env.model.getters.getOdooChartIds().length\n );\n }\n\n newText() {\n this.env.openSidePanel(\"TEXT_FILTER_SIDE_PANEL\");\n }\n\n newDate() {\n this.env.openSidePanel(\"DATE_FILTER_SIDE_PANEL\");\n }\n\n newRelation() {\n this.env.openSidePanel(\"RELATION_FILTER_SIDE_PANEL\");\n }\n\n /**\n * @param {string} id\n */\n onEdit(id) {\n const filter = this.env.model.getters.getGlobalFilter(id);\n if (!filter) {\n return;\n }\n switch (filter.type) {\n case \"text\":\n this.env.openSidePanel(\"TEXT_FILTER_SIDE_PANEL\", { id });\n break;\n case \"date\":\n this.env.openSidePanel(\"DATE_FILTER_SIDE_PANEL\", { id });\n break;\n case \"relation\":\n this.env.openSidePanel(\"RELATION_FILTER_SIDE_PANEL\", { id });\n break;\n }\n }\n}\n\nGlobalFiltersSidePanel.template = \"spreadsheet_edition.GlobalFiltersSidePanel\";\nGlobalFiltersSidePanel.components = { FilterValue };\nGlobalFiltersSidePanel.props = {\n onCloseSidePanel: { type: Function, optional: true },\n};\n", "/** @odoo-module */\n\nimport { _t, _lt } from \"@web/core/l10n/translation\";\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nimport GlobalFiltersSidePanel from \"./global_filters_side_panel\";\nimport { FilterComponent } from \"./filter_component\";\n\nimport \"./operational_transform\";\nimport DateFilterEditorSidePanel from \"./components/filter_editor/date_filter_editor_side_panel\";\nimport TextFilterEditorSidePanel from \"./components/filter_editor/text_filter_editor_side_panel\";\nimport RelationFilterEditorSidePanel from \"./components/filter_editor/relation_filter_editor_side_panel\";\n\nconst { sidePanelRegistry, topbarComponentRegistry, cellMenuRegistry } = spreadsheet.registries;\n\nsidePanelRegistry.add(\"DATE_FILTER_SIDE_PANEL\", {\n title: _t(\"Filter properties\"),\n Body: DateFilterEditorSidePanel,\n});\n\nsidePanelRegistry.add(\"TEXT_FILTER_SIDE_PANEL\", {\n title: _t(\"Filter properties\"),\n Body: TextFilterEditorSidePanel,\n});\n\nsidePanelRegistry.add(\"RELATION_FILTER_SIDE_PANEL\", {\n title: _t(\"Filter properties\"),\n Body: RelationFilterEditorSidePanel,\n});\n\nsidePanelRegistry.add(\"GLOBAL_FILTERS_SIDE_PANEL\", {\n title: _t(\"Filters\"),\n Body: GlobalFiltersSidePanel,\n});\n\ntopbarComponentRegistry.add(\"filter_component\", {\n component: FilterComponent,\n isVisible: (env) => {\n return !env.model.getters.isReadonly() || env.model.getters.getGlobalFilters().length;\n },\n});\n\ncellMenuRegistry.add(\"use_global_filter\", {\n name: _lt(\"Set as filter\"),\n sequence: 175,\n action(env) {\n const position = env.model.getters.getActivePosition();\n const cell = env.model.getters.getCell(position);\n const filters = env.model.getters.getFiltersMatchingPivot(cell.content);\n env.model.dispatch(\"SET_MANY_GLOBAL_FILTER_VALUE\", { filters });\n },\n isVisible: (env) => {\n const position = env.model.getters.getActivePosition();\n const cell = env.model.getters.getCell(position);\n if (!cell) {\n return false;\n }\n const filters = env.model.getters.getFiltersMatchingPivot(cell.content);\n return filters.length > 0;\n },\n});\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nconst { otRegistry } = spreadsheet.registries;\n\notRegistry.addTransformation(\n \"REMOVE_GLOBAL_FILTER\",\n [\"EDIT_GLOBAL_FILTER\"],\n (toTransform, executed) =>\n toTransform.id === executed.id ? undefined : toTransform\n);\n", "/** @odoo-module */\n\n/**\n * see https://stackoverflow.com/a/30106551\n * @param {string} string\n * @returns {string}\n */\nfunction base64ToUtf8(str) {\n // Going backwards: from bytestream, to percent-encoding, to original string.\n return decodeURIComponent(\n atob(str)\n .split(\"\")\n .map((c) => \"%\" + (\"00\" + c.charCodeAt(0).toString(16)).slice(-2))\n .join(\"\")\n );\n}\n\n/**\n * see https://stackoverflow.com/a/30106551\n * @param {string} string\n * @returns {string}\n */\nfunction utf8ToBase64(str) {\n // first we use encodeURIComponent to get percent-encoded UTF-8,\n // then we convert the percent encodings into raw bytes which\n // can be fed into btoa.\n return btoa(\n encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function toSolidBytes(match, p1) {\n return String.fromCharCode(\"0x\" + p1);\n })\n );\n}\n\n/**\n * Encode a json to a base64 string\n * @param {object} json\n */\nexport function jsonToBase64(json) {\n return utf8ToBase64(JSON.stringify(json));\n}\n\n/**\n * Decode a base64 encoded json\n * @param {string} string\n */\nexport function base64ToJson(string) {\n return JSON.parse(base64ToUtf8(string));\n}\n", "/** @odoo-module **/\n\n/**\n * @typedef {import(\"@web/core/orm_service\").ORM} ORM\n */\n\n/**\n *\n * Upload files on the server and link the files to a record as attachment.\n *\n * Implements the `FileStore` interface defined by o-spreadsheet.\n * https://github.com/odoo/o-spreadsheet/blob/300da461b23b5f3db017270192893d4a972bacf0/src/types/files.ts#L4\n *\n */\nexport class RecordFileStore {\n /**\n *\n * @param {string} resModel\n * @param {number} resId\n * @param {*} http\n * @param {ORM} orm\n */\n constructor(resModel, resId, http, orm) {\n this.resModel = resModel;\n this.resId = resId;\n this.http = http;\n this.orm = orm;\n }\n\n /**\n * Upload a file on the server and returns the path to the file.\n */\n async upload(file) {\n const route = \"/web/binary/upload_attachment\";\n const params = {\n ufile: [file],\n csrf_token: odoo.csrf_token,\n model: this.resModel,\n id: this.resId,\n };\n const fileData = JSON.parse(await this.http.post(route, params, \"text\"))[0];\n return \"/web/image/\" + fileData.id;\n }\n\n /**\n * @param {string} path\n * @returns {Promise}\n */\n async delete(path) {\n const attachmentId = path.split(\"/\").pop();\n if (Number.isNaN(attachmentId)) {\n throw new Error(\"Invalid path: \" + path);\n }\n await this.orm.unlink(\"ir.attachment\", [parseInt(attachmentId)]);\n }\n}\n", "/** @odoo-module */\n\nimport { _lt } from \"@web/core/l10n/translation\";\nimport spreadsheet, {\n initCallbackRegistry,\n} from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport {\n buildIrMenuIdLink,\n buildViewLink,\n buildIrMenuXmlLink,\n} from \"@spreadsheet/ir_ui_menu/odoo_menu_link_cell\";\nimport { IrMenuSelectorDialog } from \"@spreadsheet_edition/assets/components/ir_menu_selector/ir_menu_selector\";\n\nconst { markdownLink } = spreadsheet.links;\nconst { linkMenuRegistry } = spreadsheet.registries;\n\n/**\n * Helper to get the function to be called when the spreadsheet is opened\n * in order to insert the link.\n * @param {import(\"@spreadsheet/ir_ui_menu/odoo_menu_link_cell\").ViewLinkDescription} actionToLink\n * @returns Function to call\n */\nfunction insertLink(actionToLink) {\n return (model) => {\n if (!this.isEmptySpreadsheet) {\n const sheetId = model.uuidGenerator.uuidv4();\n const sheetIdFrom = model.getters.getActiveSheetId();\n model.dispatch(\"CREATE_SHEET\", {\n sheetId,\n position: model.getters.getSheetIds().length,\n });\n model.dispatch(\"ACTIVATE_SHEET\", { sheetIdFrom, sheetIdTo: sheetId });\n }\n const viewLink = buildViewLink(actionToLink);\n model.dispatch(\"UPDATE_CELL\", {\n sheetId: model.getters.getActiveSheetId(),\n content: markdownLink(actionToLink.name, viewLink),\n col: 0,\n row: 0,\n });\n };\n}\n\ninitCallbackRegistry.add(\"insertLink\", insertLink);\n\nlinkMenuRegistry.add(\"odooMenu\", {\n name: _lt(\"Link an Odoo menu\"),\n sequence: 20,\n action: async (env) => {\n return new Promise((resolve) => {\n const closeDialog = env.services.dialog.add(IrMenuSelectorDialog, {\n onMenuSelected: (menuId) => {\n closeDialog();\n const menu = env.services.menu.getMenu(menuId);\n const xmlId = menu && menu.xmlid;\n const url = xmlId ? buildIrMenuXmlLink(xmlId) : buildIrMenuIdLink(menuId);\n const label = menu.name;\n resolve(markdownLink(label, url));\n },\n });\n });\n },\n});\n", "/** @odoo-module */\n\nimport { getNumberOfListFormulas } from \"@spreadsheet/list/list_helpers\";\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nconst { autofillModifiersRegistry, autofillRulesRegistry } = spreadsheet.registries;\n\n//--------------------------------------------------------------------------\n// Autofill Rules\n//--------------------------------------------------------------------------\n\nautofillRulesRegistry.add(\"autofill_list\", {\n condition: (cell) => cell && cell.isFormula && getNumberOfListFormulas(cell.content) === 1,\n generateRule: (cell, cells) => {\n const increment = cells.filter(\n (cell) => cell && cell.isFormula && getNumberOfListFormulas(cell.content) === 1\n ).length;\n return { type: \"LIST_UPDATER\", increment, current: 0 };\n },\n sequence: 3,\n});\n\n//--------------------------------------------------------------------------\n// Autofill Modifier\n//--------------------------------------------------------------------------\n\nautofillModifiersRegistry.add(\"LIST_UPDATER\", {\n apply: (rule, data, getters, direction) => {\n rule.current += rule.increment;\n let isColumn;\n let steps;\n switch (direction) {\n case \"up\":\n isColumn = false;\n steps = -rule.current;\n break;\n case \"down\":\n isColumn = false;\n steps = rule.current;\n break;\n case \"left\":\n isColumn = true;\n steps = -rule.current;\n break;\n case \"right\":\n isColumn = true;\n steps = rule.current;\n }\n const content = getters.getNextListValue(\n getters.getFormulaCellContent(data.sheetId, data.cell),\n isColumn,\n steps\n );\n let tooltip = {\n props: {\n content,\n },\n };\n if (content && content !== data.content) {\n tooltip = {\n props: {\n content: getters.getTooltipListFormula(content, isColumn),\n },\n };\n }\n return {\n cellData: {\n style: undefined,\n format: undefined,\n border: undefined,\n content,\n },\n tooltip,\n };\n },\n});\n", "/** @odoo-module */\n\nimport { _t, _lt } from \"@web/core/l10n/translation\";\n\nimport spreadsheet, {\n initCallbackRegistry,\n} from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nimport \"./autofill\";\nimport \"./operational_transform\";\n\nimport ListingAllSidePanel from \"./side_panels/listing_all_side_panel\";\nimport ListAutofillPlugin from \"./plugins/list_autofill_plugin\";\n\nimport { insertList } from \"./list_init_callback\";\n\nconst { featurePluginRegistry, sidePanelRegistry, cellMenuRegistry } = spreadsheet.registries;\n\nfeaturePluginRegistry.add(\"odooListAutofillPlugin\", ListAutofillPlugin);\n\nsidePanelRegistry.add(\"LIST_PROPERTIES_PANEL\", {\n title: () => _t(\"List properties\"),\n Body: ListingAllSidePanel,\n});\n\ninitCallbackRegistry.add(\"insertList\", insertList);\n\ncellMenuRegistry.add(\"listing_properties\", {\n name: _lt(\"See list properties\"),\n sequence: 190,\n action(env) {\n const position = env.model.getters.getActivePosition();\n const listId = env.model.getters.getListIdFromPosition(position);\n env.model.dispatch(\"SELECT_ODOO_LIST\", { listId });\n env.openSidePanel(\"LIST_PROPERTIES_PANEL\", {});\n },\n isVisible: (env) => {\n const position = env.model.getters.getActivePosition();\n return env.model.getters.getListIdFromPosition(position) !== undefined;\n },\n});\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nconst { createFullMenuItem } = spreadsheet.helpers;\n\nexport const REINSERT_LIST_CHILDREN = (env) =>\n env.model.getters.getListIds().map((listId, index) => {\n return createFullMenuItem(`reinsert_list_${listId}`, {\n name: env.model.getters.getListDisplayName(listId),\n sequence: index,\n action: async (env) => {\n const zone = env.model.getters.getSelectedZone();\n const dataSource = await env.model.getters.getAsyncListDataSource(listId);\n const list = env.model.getters.getListDefinition(listId);\n const columns = list.columns.map((name) => ({\n name,\n type: dataSource.getField(name).type,\n }));\n env.getLinesNumber((linesNumber) => {\n env.model.dispatch(\"RE_INSERT_ODOO_LIST\", {\n sheetId: env.model.getters.getActiveSheetId(),\n col: zone.left,\n row: zone.top,\n id: listId,\n linesNumber,\n columns: columns,\n });\n });\n },\n });\n });\n", "/** @odoo-module **/\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport ListDataSource from \"@spreadsheet/list/list_data_source\";\n\nconst uuidGenerator = new spreadsheet.helpers.UuidGenerator();\n\n/**\n * Get the function that have to be executed to insert the given list in the\n * given spreadsheet. The returned function has to be called with the model\n * of the spreadsheet and the dataSource of this list\n *\n * @private\n *\n * @param {import(\"@spreadsheet/list/plugins/list_core_plugin\").SpreadsheetList} list\n * @param {object} param\n * @param {number} param.threshold\n * @param {object} param.fields fields coming from list_model\n * @param {string} param.name Name of the list\n *\n * @returns {function}\n */\nexport function insertList({ list, threshold, fields, name }) {\n const definition = {\n metaData: {\n resModel: list.model,\n columns: list.columns.map((column) => column.name),\n fields,\n },\n searchParams: {\n domain: list.domain,\n context: list.context,\n orderBy: list.orderBy,\n },\n name,\n };\n return async (model) => {\n const dataSourceId = uuidGenerator.uuidv4();\n model.config.custom.dataSources.add(dataSourceId, ListDataSource, {\n ...definition,\n limit: threshold,\n });\n await model.config.custom.dataSources.load(dataSourceId);\n if (!this.isEmptySpreadsheet) {\n const sheetId = uuidGenerator.uuidv4();\n const sheetIdFrom = model.getters.getActiveSheetId();\n model.dispatch(\"CREATE_SHEET\", {\n sheetId,\n position: model.getters.getSheetIds().length,\n });\n model.dispatch(\"ACTIVATE_SHEET\", { sheetIdFrom, sheetIdTo: sheetId });\n }\n const defWithoutFields = JSON.parse(JSON.stringify(definition));\n defWithoutFields.metaData.fields = undefined;\n const sheetId = model.getters.getActiveSheetId();\n model.dispatch(\"INSERT_ODOO_LIST\", {\n sheetId,\n col: 0,\n row: 0,\n id: model.getters.getNextListId(),\n definition: defWithoutFields,\n dataSourceId,\n linesNumber: threshold,\n columns: list.columns,\n });\n const columns = [];\n for (let col = 0; col < list.columns.length; col++) {\n columns.push(col);\n }\n model.dispatch(\"AUTORESIZE_COLUMNS\", { sheetId, cols: columns });\n };\n}\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nconst { otRegistry } = spreadsheet.registries;\n\notRegistry\n\n .addTransformation(\"INSERT_ODOO_LIST\", [\"INSERT_ODOO_LIST\"], (toTransform) => ({\n ...toTransform,\n id: (parseInt(toTransform.id, 10) + 1).toString(),\n }))\n .addTransformation(\"REMOVE_ODOO_LIST\", [\"RENAME_ODOO_LIST\"], (toTransform, executed) => {\n if (toTransform.listId === executed.listId) {\n return undefined;\n }\n return toTransform;\n })\n .addTransformation(\"REMOVE_ODOO_LIST\", [\"RE_INSERT_ODOO_LIST\"], (toTransform, executed) => {\n if (toTransform.id === executed.listId) {\n return undefined;\n }\n return toTransform;\n });\n", "/** @odoo-module */\n\nimport { _t } from \"web.core\";\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { getFirstListFunction, getNumberOfListFormulas } from \"@spreadsheet/list/list_helpers\";\n\nconst { astToFormula } = spreadsheet;\n\nexport default class ListAutofillPlugin extends spreadsheet.UIPlugin {\n // ---------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------\n\n /**\n * Get the next value to autofill of a list function\n *\n * @param {string} formula List formula\n * @param {boolean} isColumn True if autofill is LEFT/RIGHT, false otherwise\n * @param {number} increment number of steps\n *\n * @returns Autofilled value\n */\n getNextListValue(formula, isColumn, increment) {\n if (getNumberOfListFormulas(formula) !== 1) {\n return formula;\n }\n const { functionName, args } = getFirstListFunction(formula);\n const evaluatedArgs = args\n .map(astToFormula)\n .map((arg) => this.getters.evaluateFormula(arg));\n const listId = evaluatedArgs[0];\n const columns = this.getters.getListDefinition(listId).columns;\n if (functionName === \"ODOO.LIST\") {\n const position = parseInt(evaluatedArgs[1], 10);\n const field = evaluatedArgs[2];\n if (isColumn) {\n /** Change the field */\n const index = columns.findIndex((col) => col === field) + increment;\n if (index < 0 || index >= columns.length) {\n return \"\";\n }\n return this._getListFunction(listId, position, columns[index]);\n } else {\n /** Change the position */\n const nextPosition = position + increment;\n if (nextPosition === 0) {\n return this._getListHeaderFunction(listId, field);\n }\n if (nextPosition < 0) {\n return \"\";\n }\n return this._getListFunction(listId, nextPosition, field);\n }\n }\n if (functionName === \"ODOO.LIST.HEADER\") {\n const field = evaluatedArgs[1];\n if (isColumn) {\n /** Change the field */\n const index = columns.findIndex((col) => col === field) + increment;\n if (index < 0 || index >= columns.length) {\n return \"\";\n }\n return this._getListHeaderFunction(listId, columns[index]);\n } else {\n /** If down, set position */\n if (increment > 0) {\n return this._getListFunction(listId, increment, field);\n }\n return \"\";\n }\n }\n return formula;\n }\n\n /**\n * Compute the tooltip to display from a Pivot formula\n *\n * @param {string} formula Pivot formula\n * @param {boolean} isColumn True if the direction is left/right, false\n * otherwise\n */\n getTooltipListFormula(formula, isColumn) {\n if (!formula) {\n return [];\n }\n const { functionName, args } = getFirstListFunction(formula);\n const evaluatedArgs = args\n .map(astToFormula)\n .map((arg) => this.getters.evaluateFormula(arg));\n if (isColumn || functionName === \"ODOO.LIST.HEADER\") {\n const fieldName = functionName === \"ODOO.LIST\" ? evaluatedArgs[2] : evaluatedArgs[1];\n return this.getters.getListDataSource(evaluatedArgs[0]).getListHeaderValue(fieldName);\n }\n return _t(\"Record #\") + evaluatedArgs[1];\n }\n\n _getListFunction(listId, position, field) {\n return `=ODOO.LIST(${listId},${position},\"${field}\")`;\n }\n\n _getListHeaderFunction(listId, field) {\n return `=ODOO.LIST.HEADER(${listId},\"${field}\")`;\n }\n}\n\nListAutofillPlugin.getters = [\"getNextListValue\", \"getTooltipListFormula\"];\n", "/** @odoo-module */\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport { ListingDetailsSidePanel } from \"./listing_details_side_panel\";\n\nconst { Component } = owl;\n\nexport default class ListingAllSidePanel extends Component {\n constructor() {\n super(...arguments);\n this.getters = this.env.model.getters;\n }\n\n selectListing(listId) {\n this.env.model.dispatch(\"SELECT_ODOO_LIST\", { listId });\n }\n\n resetListingSelection() {\n this.env.model.dispatch(\"SELECT_ODOO_LIST\");\n }\n\n delete(listId) {\n this.env.askConfirmation(_t(\"Are you sure you want to delete this list ?\"), () => {\n this.env.model.dispatch(\"REMOVE_ODOO_LIST\", { listId });\n this.props.onCloseSidePanel();\n });\n }\n}\nListingAllSidePanel.template = \"spreadsheet_edition.ListingAllSidePanel\";\nListingAllSidePanel.components = { ListingDetailsSidePanel };\n", "/** @odoo-module */\n\nimport { Domain } from \"@web/core/domain\";\nimport { DomainSelector } from \"@web/core/domain_selector/domain_selector\";\nimport { DomainSelectorDialog } from \"@web/core/domain_selector_dialog/domain_selector_dialog\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { _t } from \"web.core\";\nimport { time_to_str } from \"web.time\";\n\nimport EditableName from \"../../o_spreadsheet/editable_name/editable_name\";\n\nconst { Component, onWillStart, onWillUpdateProps } = owl;\n\nexport class ListingDetailsSidePanel extends Component {\n setup() {\n this.getters = this.env.model.getters;\n this.dialog = useService(\"dialog\");\n const loadData = async () => {\n this.dataSource = await this.env.model.getters.getAsyncListDataSource(\n this.props.listId\n );\n this.modelDisplayName = await this.dataSource.getModelLabel();\n };\n onWillStart(loadData);\n onWillUpdateProps(loadData);\n }\n\n get listDefinition() {\n const listId = this.props.listId;\n const def = this.getters.getListDefinition(listId);\n return {\n model: def.model,\n modelDisplayName: this.modelDisplayName,\n domain: new Domain(def.domain).toString(),\n orderBy: def.orderBy,\n };\n }\n\n formatSort(sort) {\n return `${this.dataSource.getListHeaderValue(sort.name)} (${\n sort.asc ? _t(\"ascending\") : _t(\"descending\")\n })`;\n }\n\n getLastUpdate() {\n const lastUpdate = this.dataSource.lastUpdate;\n if (lastUpdate) {\n return time_to_str(new Date(lastUpdate));\n }\n return _t(\"never\");\n }\n\n onNameChanged(name) {\n this.env.model.dispatch(\"RENAME_ODOO_LIST\", {\n listId: this.props.listId,\n name,\n });\n }\n\n async refresh() {\n this.env.model.dispatch(\"REFRESH_ODOO_LIST\", { listId: this.props.listId });\n this.env.model.dispatch(\"EVALUATE_CELLS\", { sheetId: this.getters.getActiveSheetId() });\n }\n\n openDomainEdition() {\n this.dialog.add(DomainSelectorDialog, {\n resModel: this.listDefinition.model,\n initialValue: this.listDefinition.domain,\n readonly: false,\n isDebugMode: !!this.env.debug,\n onSelected: (domain) =>\n this.env.model.dispatch(\"UPDATE_ODOO_LIST_DOMAIN\", {\n listId: this.props.listId,\n domain: new Domain(domain).toList(),\n }),\n });\n }\n}\nListingDetailsSidePanel.template = \"spreadsheet_edition.ListingDetailsSidePanel\";\nListingDetailsSidePanel.components = { DomainSelector, EditableName };\nListingDetailsSidePanel.props = {\n listId: {\n type: String,\n optional: true,\n },\n};\n", "/** @odoo-module **/\n\n/**\n * This class implements the `TransportService` interface defined\n * by o-spreadsheet. Its purpose is to communicate with other clients\n * by sending and receiving spreadsheet messages through the server.\n * @see https://github.com/odoo/o-spreadsheet\n *\n * It listens messages on the long polling bus and forwards spreadsheet messages\n * to the handler. (note: it is assumed there is only one handler)\n *\n * It uses the RPC protocol to send messages to the server which\n * push them in the long polling bus for other clients.\n */\nexport default class SpreadsheetCollaborativeChannel {\n /**\n * @param {Env} env\n * @param {string} resModel model linked to the spreadsheet\n * @param {number} resId Id of the spreadsheet\n */\n constructor(env, resModel, resId) {\n this.env = env;\n this.resId = resId;\n this.resModel = resModel;\n /**\n * A callback function called to handle messages when they are received.\n */\n this._listener;\n /**\n * Messages are queued while there is no listener. They are forwarded\n * once it registers.\n */\n this._queue = [];\n // Listening this channel tells the server the spreadsheet is active\n // but the server will actually push to channel [{dbname}, {resModel}, {resId}]\n // The user can listen to this channel only if he has the required read access.\n this._channel = `spreadsheet_collaborative_session:${this.resModel}:${this.resId}`;\n this.env.services.bus_service.addChannel(this._channel);\n this.env.services.bus_service.addEventListener('notification', ({ detail: notifs }) =>\n this._handleNotifications(this._filterSpreadsheetNotifs(notifs))\n );\n }\n\n /**\n * Register a function that is called whenever a new spreadsheet revision\n * message notification is received by server.\n *\n * @param {any} id\n * @param {Function} callback\n */\n onNewMessage(id, callback) {\n this._listener = callback;\n for (let message of this._queue) {\n callback(message);\n }\n this._queue = [];\n }\n\n /**\n * Send a message to the server\n *\n * @param {Object} message\n */\n sendMessage(message) {\n return this.env.services.rpc({\n model: this.resModel,\n method: \"dispatch_spreadsheet_message\",\n args: [this.resId, message],\n }, { shadow: true });\n }\n\n /**\n * Stop listening new messages\n */\n leave() {\n this._listener = undefined;\n }\n\n /**\n * Filters the received messages to only handle the messages related to\n * spreadsheet\n *\n * @private\n * @param {Array} notifs\n *\n * @returns {Array} notifs which are related to spreadsheet\n */\n _filterSpreadsheetNotifs(notifs) {\n return notifs.filter((notification) => {\n const { payload, type } = notification;\n return type === 'spreadsheet' && payload.id === this.resId;\n });\n }\n\n /**\n * Either forward the message to the listener if it's already registered,\n * or put it in a queue.\n *\n * @private\n * @param {Array} notifs\n */\n _handleNotifications(notifs) {\n for (const { payload } of notifs) {\n if (!this._listener) {\n this._queue.push(payload);\n } else {\n this._listener(payload);\n }\n }\n }\n}\n", "/** @odoo-module */\n\nimport { registry } from \"@web/core/registry\";\nimport SpreadsheetCollaborativeChannel from \"./spreadsheet_collaborative_channel\";\n\nexport class SpreadsheetCollaborativeService {\n /**\n * Get a new collaborative channel for the given spreadsheet id\n * @param {Env} env Env of owl (Component.env)\n * @param {string} resModel model linked to the spreadsheet\n * @param {number} resId id of the spreadsheet\n */\n getCollaborativeChannel(env, resModel, resId) {\n return new SpreadsheetCollaborativeChannel(env, resModel, resId);\n }\n}\n\n/**\n * This service exposes a single instance of the above class.\n */\nexport const spreadsheetCollaborativeService = {\n dependencies: ['bus_service'],\n start(env, dependencies) {\n return new SpreadsheetCollaborativeService(env, dependencies);\n },\n};\n\nregistry.category(\"services\").add(\"spreadsheet_collaborative\", spreadsheetCollaborativeService);\n", "/** @odoo-module */\n\nconst { Component, useState } = owl;\n\nexport default class EditableName extends Component {\n setup() {\n super.setup();\n this.state = useState({\n isEditing: false,\n name: \"\",\n });\n }\n\n rename() {\n this.state.isEditing = true;\n this.state.name = this.props.name;\n }\n\n save() {\n this.props.onChanged(this.state.name.trim());\n this.state.isEditing = false;\n }\n}\n\nEditableName.template = \"spreadsheet_edition.EditableName\";\nEditableName.props = {\n name: String,\n displayName: String,\n onChanged: Function,\n};\n", "/** @odoo-module */\n\nimport { _t, _lt } from \"web.core\";\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { REINSERT_LIST_CHILDREN } from \"../list/list_actions\";\nimport { INSERT_PIVOT_CELL_CHILDREN, REINSERT_PIVOT_CHILDREN } from \"../pivot/pivot_actions\";\nconst { topbarMenuRegistry } = spreadsheet.registries;\nconst { createFullMenuItem } = spreadsheet.helpers;\n\n//--------------------------------------------------------------------------\n// Spreadsheet context menu items\n//--------------------------------------------------------------------------\n\ntopbarMenuRegistry.add(\"file\", { name: _t(\"File\"), sequence: 10 });\ntopbarMenuRegistry.addChild(\"new_sheet\", [\"file\"], {\n name: _lt(\"New\"),\n sequence: 10,\n isVisible: (env) => !env.isDashboardSpreadsheet,\n action: (env) => env.newSpreadsheet(),\n});\ntopbarMenuRegistry.addChild(\"make_copy\", [\"file\"], {\n name: _lt(\"Make a copy\"),\n sequence: 20,\n isVisible: (env) => !env.isDashboardSpreadsheet,\n action: (env) => env.makeCopy(),\n});\ntopbarMenuRegistry.addChild(\"save_as_template\", [\"file\"], {\n name: _lt(\"Save as template\"),\n sequence: 40,\n isVisible: (env) => !env.isDashboardSpreadsheet,\n action: (env) => env.saveAsTemplate(),\n});\ntopbarMenuRegistry.addChild(\"download\", [\"file\"], {\n name: _lt(\"Download\"),\n sequence: 50,\n action: (env) => env.download(),\n isReadonlyAllowed: true,\n});\n\ntopbarMenuRegistry.addChild(\"clear_history\", [\"file\"], {\n name: _lt(\"Clear history\"),\n sequence: 60,\n isVisible: (env) => env.debug,\n action: (env) => {\n env.model.session.snapshot(env.model.exportData());\n env.model.garbageCollectExternalResources();\n window.location.reload();\n },\n});\n\ntopbarMenuRegistry.addChild(\"data_sources_data\", [\"data\"], (env) => {\n const pivots = env.model.getters.getPivotIds();\n const children = pivots.map((pivotId, index) =>\n createFullMenuItem(`item_pivot_${pivotId}`, {\n name: env.model.getters.getPivotDisplayName(pivotId),\n sequence: 100 + index,\n action: (env) => {\n env.model.dispatch(\"SELECT_PIVOT\", { pivotId: pivotId });\n env.openSidePanel(\"PIVOT_PROPERTIES_PANEL\", {});\n },\n icon: \"fa fa-table\",\n separator: index === env.model.getters.getPivotIds().length - 1,\n })\n );\n const lists = env.model.getters.getListIds().map((listId, index) => {\n return createFullMenuItem(`item_list_${listId}`, {\n name: env.model.getters.getListDisplayName(listId),\n sequence: 100 + index + pivots.length,\n action: (env) => {\n env.model.dispatch(\"SELECT_ODOO_LIST\", { listId: listId });\n env.openSidePanel(\"LIST_PROPERTIES_PANEL\", {});\n },\n icon: \"fa fa-list\",\n separator: index === env.model.getters.getListIds().length - 1,\n });\n });\n return children.concat(lists).concat([\n createFullMenuItem(`refresh_all_data`, {\n name: _t(\"Refresh all data\"),\n sequence: 1000,\n action: (env) => {\n env.model.dispatch(\"REFRESH_ALL_DATA_SOURCES\");\n },\n separator: true,\n }),\n createFullMenuItem(`reinsert_pivot`, {\n name: _t(\"Re-insert pivot\"),\n sequence: 1010,\n children: [REINSERT_PIVOT_CHILDREN],\n isVisible: (env) => env.model.getters.getPivotIds().length,\n }),\n createFullMenuItem(`insert_pivot_cell`, {\n name: _t(\"Insert pivot cell\"),\n sequence: 1020,\n children: [INSERT_PIVOT_CELL_CHILDREN],\n isVisible: (env) => env.model.getters.getPivotIds().length,\n }),\n createFullMenuItem(`reinsert_list`, {\n name: _t(\"Re-insert list\"),\n sequence: 1021,\n children: [REINSERT_LIST_CHILDREN],\n isVisible: (env) => env.model.getters.getListIds().length,\n }),\n ]);\n});\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nconst { Component } = owl;\nconst { autofillModifiersRegistry, autofillRulesRegistry } = spreadsheet.registries;\n\n//--------------------------------------------------------------------------\n// Autofill Component\n//--------------------------------------------------------------------------\nexport class AutofillTooltip extends Component {}\nAutofillTooltip.template = \"spreadsheet_edition.AutofillTooltip\";\n\n//--------------------------------------------------------------------------\n// Autofill Rules\n//--------------------------------------------------------------------------\n\nautofillRulesRegistry\n .add(\"autofill_pivot\", {\n condition: (cell) => cell && cell.isFormula && cell.content.match(/=\\s*ODOO\\.PIVOT/),\n generateRule: (cell, cells) => {\n const increment = cells.filter(\n (cell) => cell && cell.isFormula && cell.content.match(/=\\s*ODOO\\.PIVOT/)\n ).length;\n return { type: \"PIVOT_UPDATER\", increment, current: 0 };\n },\n sequence: 2,\n })\n .add(\"autofill_pivot_position\", {\n condition: (cell) =>\n cell && cell.isFormula && cell.content.match(/=.*ODOO\\.PIVOT.*ODOO\\.PIVOT\\.POSITION/),\n generateRule: () => ({ type: \"PIVOT_POSITION_UPDATER\", current: 0 }),\n sequence: 1,\n });\n\n//--------------------------------------------------------------------------\n// Autofill Modifier\n//--------------------------------------------------------------------------\n\nautofillModifiersRegistry\n .add(\"PIVOT_UPDATER\", {\n apply: (rule, data, getters, direction) => {\n rule.current += rule.increment;\n let isColumn;\n let steps;\n switch (direction) {\n case \"up\":\n isColumn = false;\n steps = -rule.current;\n break;\n case \"down\":\n isColumn = false;\n steps = rule.current;\n break;\n case \"left\":\n isColumn = true;\n steps = -rule.current;\n break;\n case \"right\":\n isColumn = true;\n steps = rule.current;\n }\n const content = getters.getPivotNextAutofillValue(\n getters.getFormulaCellContent(data.sheetId, data.cell),\n isColumn,\n steps\n );\n let tooltip = {\n props: {\n content: data.content,\n },\n };\n if (content && content !== data.content) {\n tooltip = {\n props: {\n content: getters.getTooltipFormula(content, isColumn),\n },\n component: AutofillTooltip,\n };\n }\n if (!content) {\n tooltip = undefined;\n }\n return {\n cellData: {\n style: undefined,\n format: undefined,\n border: undefined,\n content,\n },\n tooltip,\n };\n },\n })\n .add(\"PIVOT_POSITION_UPDATER\", {\n /**\n * Increment (or decrement) positions in template pivot formulas.\n * Autofilling vertically increments the field of the deepest row\n * group of the formula. Autofilling horizontally does the same for\n * column groups.\n */\n apply: (rule, data, getters, direction) => {\n const formulaString = data.cell.content;\n const pivotId = formulaString.match(/ODOO\\.PIVOT\\.POSITION\\(\\s*\"(\\w+)\"\\s*,/)[1];\n if (!getters.isExistingPivot(pivotId)) {\n return { cellData: { ...data.cell, content: formulaString } };\n }\n const pivotDefinition = getters.getPivotDefinition(pivotId);\n const fields = [\"up\", \"down\"].includes(direction)\n ? pivotDefinition.rowGroupBys\n : pivotDefinition.colGroupBys;\n const step = [\"right\", \"down\"].includes(direction) ? 1 : -1;\n\n const field = fields\n .reverse()\n .find((field) =>\n new RegExp(`ODOO\\\\.PIVOT\\\\.POSITION.*${field}.*\\\\)`).test(formulaString)\n );\n const content = formulaString.replace(\n new RegExp(\n `(.*ODOO\\\\.PIVOT\\\\.POSITION\\\\(\\\\s*\"\\\\w\"\\\\s*,\\\\s*\"${field}\"\\\\s*,\\\\s*\"?)(\\\\d+)(.*)`\n ),\n (match, before, position, after) => {\n rule.current += step;\n return before + Math.max(parseInt(position) + rule.current, 1) + after;\n }\n );\n return {\n cellData: { ...data.cell, content },\n tooltip: content\n ? {\n props: { content },\n }\n : undefined,\n };\n },\n });\n", "/** @odoo-module */\n\nimport { _t, _lt } from \"@web/core/l10n/translation\";\n\nimport spreadsheet, {\n initCallbackRegistry,\n} from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nimport PivotAutofillPlugin from \"./plugins/pivot_autofill_plugin\";\nimport PivotSidePanel from \"./side_panels/pivot_list_side_panel\";\n\nimport \"./autofill\";\nimport \"./operational_transform\";\nimport { insertPivot } from \"./pivot_init_callback\";\nimport { pivotFormulaRegex } from \"@spreadsheet/pivot/pivot_helpers\";\n\nconst { featurePluginRegistry, sidePanelRegistry, cellMenuRegistry } = spreadsheet.registries;\n\nfeaturePluginRegistry.add(\"odooPivotAutofillPlugin\", PivotAutofillPlugin);\n\nsidePanelRegistry.add(\"PIVOT_PROPERTIES_PANEL\", {\n title: () => _t(\"Pivot properties\"),\n Body: PivotSidePanel,\n});\n\ninitCallbackRegistry.add(\"insertPivot\", insertPivot);\n\ncellMenuRegistry.add(\"pivot_properties\", {\n name: _lt(\"See pivot properties\"),\n sequence: 170,\n action(env) {\n const position = env.model.getters.getActivePosition();\n const pivotId = env.model.getters.getPivotIdFromPosition(position);\n env.model.dispatch(\"SELECT_PIVOT\", { pivotId });\n env.openSidePanel(\"PIVOT_PROPERTIES_PANEL\", {});\n },\n isVisible: (env) => {\n const cell = env.model.getters.getActiveCell();\n return cell && cell.isFormula && cell.content.match(pivotFormulaRegex);\n },\n});\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nconst { otRegistry } = spreadsheet.registries;\n\notRegistry\n .addTransformation(\"INSERT_PIVOT\", [\"INSERT_PIVOT\"], (toTransform) => ({\n ...toTransform,\n id: (parseInt(toTransform.id, 10) + 1).toString(),\n }))\n .addTransformation(\"REMOVE_PIVOT\", [\"RENAME_ODOO_PIVOT\"], (toTransform, executed) => {\n if (toTransform.pivotId === executed.pivotId) {\n return undefined;\n }\n return toTransform;\n })\n .addTransformation(\"REMOVE_PIVOT\", [\"RE_INSERT_PIVOT\"], (toTransform, executed) => {\n if (toTransform.id === executed.pivotId) {\n return undefined;\n }\n return toTransform;\n });\n", "/** @odoo-module **/\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { PivotDialog } from \"./spreadsheet_pivot_dialog\";\n\nconst { createFullMenuItem } = spreadsheet.helpers;\n\nexport const REINSERT_PIVOT_CHILDREN = (env) =>\n env.model.getters.getPivotIds().map((pivotId, index) =>\n createFullMenuItem(`reinsert_pivot_${pivotId}`, {\n name: env.model.getters.getPivotDisplayName(pivotId),\n sequence: index,\n action: async (env) => {\n const dataSource = env.model.getters.getPivotDataSource(pivotId);\n const model = await dataSource.copyModelWithOriginalDomain();\n const table = model.getTableStructure().export();\n const zone = env.model.getters.getSelectedZone();\n env.model.dispatch(\"RE_INSERT_PIVOT\", {\n id: pivotId,\n col: zone.left,\n row: zone.top,\n sheetId: env.model.getters.getActiveSheetId(),\n table,\n });\n env.model.dispatch(\"REFRESH_PIVOT\", { id: pivotId });\n },\n })\n );\n\nexport const INSERT_PIVOT_CELL_CHILDREN = (env) =>\n env.model.getters.getPivotIds().map((pivotId, index) =>\n createFullMenuItem(`insert_pivot_cell_${pivotId}`, {\n name: env.model.getters.getPivotDisplayName(pivotId),\n sequence: index,\n action: async (env) => {\n env.model.dispatch(\"REFRESH_PIVOT\", { id: pivotId });\n let { sheetId, col, row } = env.model.getters.getActivePosition();\n await env.model.getters.getAsyncPivotDataSource(pivotId);\n // make sure all cells are evaluated\n for (const sheetId of env.model.getters.getSheetIds()) {\n env.model.getters.getEvaluatedCells(sheetId);\n }\n const insertPivotValueCallback = (formula) => {\n env.model.dispatch(\"UPDATE_CELL\", {\n sheetId,\n col,\n row,\n content: formula,\n });\n };\n\n const getMissingValueDialogTitle = () => {\n const title = _t(\"Insert pivot cell\");\n const pivotTitle = getPivotTitle();\n if (pivotTitle) {\n return `${title} - ${pivotTitle}`;\n }\n return title;\n };\n\n const getPivotTitle = () => {\n if (pivotId) {\n return env.model.getters.getPivotDisplayName(pivotId);\n }\n return \"\";\n };\n\n env.services.dialog.add(PivotDialog, {\n title: getMissingValueDialogTitle(),\n pivotId,\n insertPivotValueCallback,\n getters: env.model.getters,\n });\n },\n })\n );\n", "/** @odoo-module **/\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport PivotDataSource from \"@spreadsheet/pivot/pivot_data_source\";\n\nconst uuidGenerator = new spreadsheet.helpers.UuidGenerator();\n\nexport function insertPivot(pivotData) {\n const definition = {\n metaData: {\n colGroupBys: [...pivotData.metaData.fullColGroupBys],\n rowGroupBys: [...pivotData.metaData.fullRowGroupBys],\n activeMeasures: [...pivotData.metaData.activeMeasures],\n resModel: pivotData.metaData.resModel,\n fields: pivotData.metaData.fields,\n sortedColumn: pivotData.metaData.sortedColumn,\n },\n searchParams: {\n ...pivotData.searchParams,\n // groups from the search bar are included in `fullRowGroupBys` and `fullColGroupBys`\n // but takes precedence if they are defined\n groupBy: [],\n },\n name: pivotData.name,\n };\n return async (model) => {\n const dataSourceId = uuidGenerator.uuidv4();\n model.config.custom.dataSources.add(dataSourceId, PivotDataSource, definition);\n await model.config.custom.dataSources.load(dataSourceId);\n const pivotDataSource = model.config.custom.dataSources.get(dataSourceId);\n // Add an empty sheet in the case of an existing spreadsheet.\n if (!this.isEmptySpreadsheet) {\n const sheetId = uuidGenerator.uuidv4();\n const sheetIdFrom = model.getters.getActiveSheetId();\n model.dispatch(\"CREATE_SHEET\", {\n sheetId,\n position: model.getters.getSheetIds().length,\n });\n model.dispatch(\"ACTIVATE_SHEET\", { sheetIdFrom, sheetIdTo: sheetId });\n }\n const structure = pivotDataSource.getTableStructure();\n const table = structure.export();\n const sheetId = model.getters.getActiveSheetId();\n\n const defWithoutFields = JSON.parse(JSON.stringify(definition));\n defWithoutFields.metaData.fields = undefined;\n model.dispatch(\"INSERT_PIVOT\", {\n sheetId,\n col: 0,\n row: 0,\n table,\n id: model.getters.getNextPivotId(),\n dataSourceId,\n definition: defWithoutFields,\n });\n const columns = [];\n for (let col = 0; col <= table.cols[table.cols.length - 1].length; col++) {\n columns.push(col);\n }\n model.dispatch(\"AUTORESIZE_COLUMNS\", { sheetId, cols: columns });\n };\n}\n", "/** @odoo-module */\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { FORMATS } from \"@spreadsheet/helpers/constants\";\nimport {\n getFirstPivotFunction,\n getNumberOfPivotFormulas,\n makePivotFormula,\n} from \"@spreadsheet/pivot/pivot_helpers\";\n\n/**\n * @typedef {import(\"@spreadsheet/pivot/pivot_table\").SpreadsheetPivotTable} SpreadsheetPivotTable\n */\n\nconst { astToFormula } = spreadsheet;\n\n/**\n * @typedef CurrentElement\n * @property {Array} cols\n * @property {Array} rows\n *\n * @typedef TooltipFormula\n * @property {string} value\n *\n * @typedef GroupByDate\n * @property {boolean} isDate\n * @property {string|undefined} group\n */\n\nexport default class PivotAutofillPlugin extends spreadsheet.UIPlugin {\n // ---------------------------------------------------------------------\n // Getters\n // ---------------------------------------------------------------------\n\n /**\n * Get the next value to autofill of a pivot function\n *\n * @param {string} formula Pivot formula\n * @param {boolean} isColumn True if autofill is LEFT/RIGHT, false otherwise\n * @param {number} increment number of steps\n *\n * @returns {string}\n */\n getPivotNextAutofillValue(formula, isColumn, increment) {\n if (getNumberOfPivotFormulas(formula) !== 1) {\n return formula;\n }\n const { functionName, args } = getFirstPivotFunction(formula);\n const evaluatedArgs = args\n .map(astToFormula)\n .map((arg) => this.getters.evaluateFormula(arg).toString());\n const pivotId = evaluatedArgs[0];\n const dataSource = this.getters.getPivotDataSource(pivotId);\n for (let i = evaluatedArgs.length - 1; i > 0; i--) {\n const fieldName = evaluatedArgs[i];\n if (\n fieldName.startsWith(\"#\") &&\n ((isColumn && dataSource.isColumnGroupBy(fieldName)) ||\n (!isColumn && dataSource.isRowGroupBy(fieldName)))\n ) {\n evaluatedArgs[i + 1] = parseInt(evaluatedArgs[i + 1], 10) + increment;\n if (evaluatedArgs[i + 1] < 0) {\n return formula;\n }\n if (functionName === \"ODOO.PIVOT\") {\n return makePivotFormula(\"ODOO.PIVOT\", evaluatedArgs);\n } else if (functionName === \"ODOO.PIVOT.HEADER\") {\n return makePivotFormula(\"ODOO.PIVOT.HEADER\", evaluatedArgs);\n }\n return formula;\n }\n }\n let builder;\n if (functionName === \"ODOO.PIVOT\") {\n builder = this._autofillPivotValue.bind(this);\n } else if (functionName === \"ODOO.PIVOT.HEADER\") {\n if (evaluatedArgs.length === 1) {\n // Total\n if (isColumn) {\n // LEFT-RIGHT\n builder = this._autofillPivotRowHeader.bind(this);\n } else {\n // UP-DOWN\n builder = this._autofillPivotColHeader.bind(this);\n }\n } else if (\n this.getters.getPivotDefinition(pivotId).rowGroupBys.includes(evaluatedArgs[1])\n ) {\n builder = this._autofillPivotRowHeader.bind(this);\n } else {\n builder = this._autofillPivotColHeader.bind(this);\n }\n }\n if (builder) {\n return builder(pivotId, evaluatedArgs, isColumn, increment);\n }\n return formula;\n }\n\n /**\n * Compute the tooltip to display from a Pivot formula\n *\n * @param {string} formula Pivot formula\n * @param {boolean} isColumn True if the direction is left/right, false\n * otherwise\n *\n * @returns {Array}\n */\n getTooltipFormula(formula, isColumn) {\n if (getNumberOfPivotFormulas(formula) !== 1) {\n return [];\n }\n const { functionName, args } = getFirstPivotFunction(formula);\n const evaluatedArgs = args\n .map(astToFormula)\n .map((arg) => this.getters.evaluateFormula(arg));\n const pivotId = evaluatedArgs[0];\n if (functionName === \"ODOO.PIVOT\") {\n return this._tooltipFormatPivot(pivotId, evaluatedArgs, isColumn);\n } else if (functionName === \"ODOO.PIVOT.HEADER\") {\n return this._tooltipFormatPivotHeader(pivotId, evaluatedArgs);\n }\n return [];\n }\n\n // ---------------------------------------------------------------------\n // Autofill\n // ---------------------------------------------------------------------\n\n /**\n * Get the next value to autofill from a pivot value (\"=PIVOT()\")\n *\n * Here are the possibilities:\n * 1) LEFT-RIGHT\n * - Working on a date value, with one level of group by in the header\n * => Autofill the date, without taking care of headers\n * - Targeting a row-header\n * => Creation of a PIVOT.HEADER with the value of the current rows\n * - Targeting outside the pivot (before the row header and after the\n * last col)\n * => Return empty string\n * - Targeting a value cell\n * => Autofill by changing the cols\n * 2) UP-DOWN\n * - Working on a date value, with one level of group by in the header\n * => Autofill the date, without taking care of headers\n * - Targeting a col-header\n * => Creation of a PIVOT.HEADER with the value of the current cols,\n * with the given increment\n * - Targeting outside the pivot (after the last row)\n * => Return empty string\n * - Targeting a value cell\n * => Autofill by changing the rows\n *\n * @param {string} pivotId Id of the pivot\n * @param {Array} args args of the pivot formula\n * @param {boolean} isColumn True if the direction is left/right, false\n * otherwise\n * @param {number} increment Increment of the autofill\n *\n * @private\n *\n * @returns {string}\n */\n _autofillPivotValue(pivotId, args, isColumn, increment) {\n const currentElement = this._getCurrentValueElement(pivotId, args);\n const dataSource = this.getters.getPivotDataSource(pivotId);\n const table = dataSource.getTableStructure();\n const isDate = dataSource.isGroupedOnlyByOneDate(isColumn ? \"COLUMN\" : \"ROW\");\n let cols = [];\n let rows = [];\n let measure;\n if (isColumn) {\n // LEFT-RIGHT\n rows = currentElement.rows;\n if (isDate) {\n // Date\n const group = dataSource.getGroupOfFirstDate(\"COLUMN\");\n cols = currentElement.cols;\n cols[0] = this._incrementDate(cols[0], group, increment);\n measure = cols.pop();\n } else {\n const currentColIndex = table.getColMeasureIndex(currentElement.cols);\n if (currentColIndex === -1) {\n return \"\";\n }\n const nextColIndex = currentColIndex + increment;\n if (nextColIndex === -1) {\n // Targeting row-header\n return this._autofillRowFromValue(pivotId, currentElement);\n }\n if (nextColIndex < -1 || nextColIndex >= table.getColWidth()) {\n // Outside the pivot\n return \"\";\n }\n // Targeting value\n const measureCell = table.getCellFromMeasureRowAtIndex(nextColIndex);\n cols = [...measureCell.values];\n measure = cols.pop();\n }\n } else {\n // UP-DOWN\n cols = currentElement.cols;\n if (isDate) {\n // Date\n if (currentElement.rows.length === 0) {\n return \"\";\n }\n const group = dataSource.getGroupOfFirstDate(\"ROW\");\n rows = currentElement.rows;\n rows[0] = this._incrementDate(rows[0], group, increment);\n } else {\n const currentRowIndex = table.getRowIndex(currentElement.rows);\n if (currentRowIndex === -1) {\n return \"\";\n }\n const nextRowIndex = currentRowIndex + increment;\n if (nextRowIndex < 0) {\n // Targeting col-header\n return this._autofillColFromValue(pivotId, nextRowIndex, currentElement);\n }\n if (nextRowIndex >= table.getRowHeight()) {\n // Outside the pivot\n return \"\";\n }\n // Targeting value\n rows = [...table.getCellsFromRowAtIndex(nextRowIndex).values];\n }\n measure = cols.pop();\n }\n return makePivotFormula(\"ODOO.PIVOT\", this._buildArgs(pivotId, measure, rows, cols));\n }\n /**\n * Get the next value to autofill from a pivot header (\"=PIVOT.HEADER()\")\n * which is a col.\n *\n * Here are the possibilities:\n * 1) LEFT-RIGHT\n * - Working on a date value, with one level of group by in the header\n * => Autofill the date, without taking care of headers\n * - Targeting outside (before the first col after the last col)\n * => Return empty string\n * - Targeting a col-header\n * => Creation of a PIVOT.HEADER with the value of the new cols\n * 2) UP-DOWN\n * - Working on a date value, with one level of group by in the header\n * => Replace the date in the headers and autocomplete as usual\n * - Targeting a cell (after the last col and before the last row)\n * => Autofill by adding the corresponding rows\n * - Targeting a col-header (after the first col and before the last\n * col)\n * => Creation of a PIVOT.HEADER with the value of the new cols\n * - Targeting outside the pivot (before the first col of after the\n * last row)\n * => Return empty string\n *\n * @param {string} pivotId Id of the pivot\n * @param {Array} args args of the pivot.header formula\n * @param {boolean} isColumn True if the direction is left/right, false\n * otherwise\n * @param {number} increment Increment of the autofill\n *\n * @private\n *\n * @returns {string}\n */\n _autofillPivotColHeader(pivotId, args, isColumn, increment) {\n const dataSource = this.getters.getPivotDataSource(pivotId);\n /** @type {SpreadsheetPivotTable} */\n const table = dataSource.getTableStructure();\n const currentElement = this._getCurrentHeaderElement(pivotId, args);\n const currentColIndex = table.getColMeasureIndex(currentElement.cols);\n const isDate = dataSource.isGroupedOnlyByOneDate(\"COLUMN\");\n if (isColumn) {\n // LEFT-RIGHT\n let groupValues;\n if (isDate) {\n // Date\n const group = dataSource.getGroupOfFirstDate(\"COLUMN\");\n groupValues = currentElement.cols;\n groupValues[0] = this._incrementDate(groupValues[0], group, increment);\n } else {\n const rowIndex = currentElement.cols.length - 1;\n const nextColIndex = currentColIndex + increment;\n const nextGroup = table.getNextColCell(nextColIndex, rowIndex);\n if (\n currentColIndex === -1 ||\n nextColIndex < 0 ||\n nextColIndex >= table.getColWidth() ||\n !nextGroup\n ) {\n // Outside the pivot\n return \"\";\n }\n // Targeting a col.header\n groupValues = nextGroup.values;\n }\n return makePivotFormula(\n \"ODOO.PIVOT.HEADER\",\n this._buildArgs(pivotId, undefined, [], groupValues)\n );\n } else {\n // UP-DOWN\n const rowIndex =\n currentColIndex === table.getColWidth() - 1\n ? table.getColHeight() - 2 + currentElement.cols.length\n : currentElement.cols.length - 1;\n const nextRowIndex = rowIndex + increment;\n const groupLevels = dataSource.getNumberOfColGroupBys();\n if (nextRowIndex < 0 || nextRowIndex >= groupLevels + 1 + table.getRowHeight()) {\n // Outside the pivot\n return \"\";\n }\n if (nextRowIndex >= groupLevels + 1) {\n // Targeting a value\n const rowIndex = nextRowIndex - groupLevels - 1;\n const measureCell = table.getCellFromMeasureRowAtIndex(currentColIndex);\n const cols = [...measureCell.values];\n const measure = cols.pop();\n const rows = [...table.getCellsFromRowAtIndex(rowIndex).values];\n return makePivotFormula(\n \"ODOO.PIVOT\",\n this._buildArgs(pivotId, measure, rows, cols)\n );\n } else {\n // Targeting a col.header\n const groupValues = table.getNextColCell(currentColIndex, nextRowIndex).values;\n return makePivotFormula(\n \"ODOO.PIVOT.HEADER\",\n this._buildArgs(pivotId, undefined, [], groupValues)\n );\n }\n }\n }\n /**\n * Get the next value to autofill from a pivot header (\"=PIVOT.HEADER()\")\n * which is a row.\n *\n * Here are the possibilities:\n * 1) LEFT-RIGHT\n * - Targeting outside (LEFT or after the last col)\n * => Return empty string\n * - Targeting a cell\n * => Autofill by adding the corresponding cols\n * 2) UP-DOWN\n * - Working on a date value, with one level of group by in the header\n * => Autofill the date, without taking care of headers\n * - Targeting a row-header\n * => Creation of a PIVOT.HEADER with the value of the new rows\n * - Targeting outside the pivot (before the first row of after the\n * last row)\n * => Return empty string\n *\n * @param {string} pivotId Id of the pivot\n * @param {Array} args args of the pivot.header formula\n * @param {boolean} isColumn True if the direction is left/right, false\n * otherwise\n * @param {number} increment Increment of the autofill\n *\n * @private\n *\n * @returns {string}\n */\n _autofillPivotRowHeader(pivotId, args, isColumn, increment) {\n const dataSource = this.getters.getPivotDataSource(pivotId);\n const table = dataSource.getTableStructure();\n const currentElement = this._getCurrentHeaderElement(pivotId, args);\n const currentIndex = table.getRowIndex(currentElement.rows);\n const isDate = dataSource.isGroupedOnlyByOneDate(\"ROW\");\n if (isColumn) {\n const colIndex = increment - 1;\n // LEFT-RIGHT\n if (colIndex < 0 || colIndex >= table.getColWidth()) {\n // Outside the pivot\n return \"\";\n }\n const measureCell = table.getCellFromMeasureRowAtIndex(colIndex);\n const values = [...measureCell.values];\n const measure = values.pop();\n return makePivotFormula(\n \"ODOO.PIVOT\",\n this._buildArgs(pivotId, measure, currentElement.rows, values)\n );\n } else {\n // UP-DOWN\n let rows;\n if (isDate) {\n // Date\n const group = dataSource.getGroupOfFirstDate(\"ROW\");\n rows = currentElement.rows;\n rows[0] = this._incrementDate(rows[0], group, increment);\n } else {\n const nextIndex = currentIndex + increment;\n if (currentIndex === -1 || nextIndex < 0 || nextIndex >= table.getRowHeight()) {\n return \"\";\n }\n rows = [...table.getCellsFromRowAtIndex(nextIndex).values];\n }\n return makePivotFormula(\n \"ODOO.PIVOT.HEADER\",\n this._buildArgs(pivotId, undefined, rows, [])\n );\n }\n }\n /**\n * Create a col header from a value\n *\n * @param {string} pivotId Id of the pivot\n * @param {number} nextIndex Index of the target column\n * @param {CurrentElement} currentElement Current element (rows and cols)\n *\n * @private\n *\n * @returns {string}\n */\n _autofillColFromValue(pivotId, nextIndex, currentElement) {\n const dataSource = this.getters.getPivotDataSource(pivotId);\n const table = dataSource.getTableStructure();\n const groupIndex = table.getColMeasureIndex(currentElement.cols);\n if (groupIndex < 0) {\n return \"\";\n }\n const levels = dataSource.getNumberOfColGroupBys();\n const index = levels + 1 + nextIndex;\n if (index < 0 || index >= levels + 1) {\n return \"\";\n }\n const cols = [];\n for (let i = 0; i <= index; i++) {\n cols.push(currentElement.cols[i]);\n }\n return makePivotFormula(\"ODOO.PIVOT.HEADER\", this._buildArgs(pivotId, undefined, [], cols));\n }\n /**\n * Create a row header from a value\n *\n * @param {string} pivotId Id of the pivot\n * @param {CurrentElement} currentElement Current element (rows and cols)\n *\n * @private\n *\n * @returns {string}\n */\n _autofillRowFromValue(pivotId, currentElement) {\n const rows = currentElement.rows;\n if (!rows) {\n return \"\";\n }\n return makePivotFormula(\"ODOO.PIVOT.HEADER\", this._buildArgs(pivotId, undefined, rows, []));\n }\n /**\n * Parse the arguments of a pivot function to find the col values and\n * the row values of a PIVOT.HEADER function\n *\n * @param {string} pivotId Id of the pivot\n * @param {Array} args Args of the pivot.header formula\n *\n * @private\n *\n * @returns {CurrentElement}\n */\n _getCurrentHeaderElement(pivotId, args) {\n const definition = this.getters.getPivotDefinition(pivotId);\n const values = this._parseArgs(args.slice(1));\n const cols = this._getFieldValues([...definition.colGroupBys, \"measure\"], values);\n const rows = this._getFieldValues(definition.rowGroupBys, values);\n return { cols, rows };\n }\n /**\n * Parse the arguments of a pivot function to find the col values and\n * the row values of a PIVOT function\n *\n * @param {string} pivotId Id of the pivot\n * @param {Array} args Args of the pivot formula\n *\n * @private\n *\n * @returns {CurrentElement}\n */\n _getCurrentValueElement(pivotId, args) {\n const definition = this.getters.getPivotDefinition(pivotId);\n const values = this._parseArgs(args.slice(2));\n const cols = this._getFieldValues(definition.colGroupBys, values);\n cols.push(args[1]); // measure\n const rows = this._getFieldValues(definition.rowGroupBys, values);\n return { cols, rows };\n }\n /**\n * Return the values for the fields which are present in the list of\n * fields\n *\n * ex: fields: [\"create_date\"]\n * values: { create_date: \"01/01\", stage_id: 1 }\n * => [\"01/01\"]\n *\n * @param {Array} fields List of fields\n * @param {Object} values Association field-values\n *\n * @private\n * @returns {Array}\n */\n _getFieldValues(fields, values) {\n return fields.filter((field) => field in values).map((field) => values[field]);\n }\n /**\n * Increment a date with a given increment and interval (group)\n *\n * @param {string} date\n * @param {string} group (day, week, month, ...)\n * @param {number} increment\n *\n * @private\n * @returns {string}\n */\n _incrementDate(date, group, increment) {\n const format = FORMATS[group].out;\n const interval = FORMATS[group].interval;\n const dateMoment = moment(date, format);\n return dateMoment.isValid() ? dateMoment.add(increment, interval).format(format) : date;\n }\n /**\n * Create a structure { field: value } from the arguments of a pivot\n * function\n *\n * @param {Array} args\n *\n * @private\n * @returns {Object}\n */\n _parseArgs(args) {\n const values = {};\n for (let i = 0; i < args.length; i += 2) {\n values[args[i]] = args[i + 1];\n }\n return values;\n }\n\n // ---------------------------------------------------------------------\n // Tooltips\n // ---------------------------------------------------------------------\n\n /**\n * Get the tooltip for a pivot formula\n *\n * @param {string} pivotId Id of the pivot\n * @param {Array} args\n * @param {boolean} isColumn True if the direction is left/right, false\n * otherwise\n * @private\n *\n * @returns {Array}\n */\n _tooltipFormatPivot(pivotId, args, isColumn) {\n const tooltips = [];\n const definition = this.getters.getPivotDefinition(pivotId);\n const dataSource = this.getters.getPivotDataSource(pivotId);\n const values = this._parseArgs(args.slice(2));\n for (const [fieldName, value] of Object.entries(values)) {\n if (\n (isColumn && dataSource.isColumnGroupBy(fieldName)) ||\n (!isColumn && dataSource.isRowGroupBy(fieldName))\n ) {\n tooltips.push({\n value: dataSource.getDisplayedPivotHeaderValue([fieldName, value]),\n });\n }\n }\n if (definition.measures.length !== 1 && isColumn) {\n const measure = args[1];\n tooltips.push({\n value: dataSource.getGroupByDisplayLabel(\"measure\", measure),\n });\n }\n if (!tooltips.length) {\n tooltips.push({\n value: _t(\"Total\"),\n });\n }\n return tooltips;\n }\n /**\n * Get the tooltip for a pivot header formula\n *\n * @param {string} pivotId Id of the pivot\n * @param {Array} args\n *\n * @private\n *\n * @returns {Array}\n */\n _tooltipFormatPivotHeader(pivotId, args) {\n const tooltips = [];\n const values = this._parseArgs(args.slice(1));\n const dataSource = this.getters.getPivotDataSource(pivotId);\n if (Object.keys(values).length === 0) {\n return [{ value: _t(\"Total\") }];\n }\n for (const [fieldName, value] of Object.entries(values)) {\n tooltips.push({ value: dataSource.getDisplayedPivotHeaderValue([fieldName, value]) });\n }\n return tooltips;\n }\n\n // ---------------------------------------------------------------------\n // Helpers\n // ---------------------------------------------------------------------\n\n /**\n * Create the args from pivot, measure, rows and cols\n * if measure is undefined, it's not added\n *\n * @param {string} pivotId Id of the pivot\n * @param {string} measure\n * @param {Object} rows\n * @param {Object} cols\n *\n * @private\n * @returns {Array}\n */\n _buildArgs(pivotId, measure, rows, cols) {\n const { rowGroupBys, measures } = this.getters.getPivotDefinition(pivotId);\n const args = [pivotId];\n if (measure) {\n args.push(measure);\n }\n for (const index in rows) {\n args.push(rowGroupBys[index]);\n args.push(rows[index]);\n }\n if (cols.length === 1 && measures.includes(cols[0])) {\n args.push(\"measure\");\n args.push(cols[0]);\n } else {\n const dataSource = this.getters.getPivotDataSource(pivotId);\n for (const index in cols) {\n args.push(dataSource.getGroupByAtIndex(\"COLUMN\", index) || \"measure\");\n args.push(cols[index]);\n }\n }\n return args;\n }\n}\n\nPivotAutofillPlugin.getters = [\"getPivotNextAutofillValue\", \"getTooltipFormula\"];\n", "/** @odoo-module */\n\nimport { Domain } from \"@web/core/domain\";\nimport { DomainSelector } from \"@web/core/domain_selector/domain_selector\";\nimport { DomainSelectorDialog } from \"@web/core/domain_selector_dialog/domain_selector_dialog\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { _t } from \"web.core\";\nimport { time_to_str } from \"web.time\";\nimport EditableName from \"../../o_spreadsheet/editable_name/editable_name\";\n\nconst { Component, onWillStart, onWillUpdateProps } = owl;\n\nexport default class PivotDetailsSidePanel extends Component {\n setup() {\n this.dialog = useService(\"dialog\");\n /** @type {import(\"@spreadsheet/pivot/pivot_data_source\").default} */\n this.dataSource = undefined;\n const loadData = async () => {\n this.dataSource = await this.env.model.getters.getAsyncPivotDataSource(\n this.props.pivotId\n );\n this.modelDisplayName = await this.dataSource.getModelLabel();\n };\n onWillStart(loadData);\n onWillUpdateProps(loadData);\n }\n\n get pivotDefinition() {\n const definition = this.env.model.getters.getPivotDefinition(this.props.pivotId);\n return {\n model: definition.model,\n modelDisplayName: this.modelDisplayName,\n domain: new Domain(definition.domain).toString(),\n dimensions: [...definition.rowGroupBys, ...definition.colGroupBys].map((fieldName) =>\n this.dataSource.getFormattedGroupBy(fieldName)\n ),\n measures: definition.measures.map((measure) =>\n this.dataSource.getGroupByDisplayLabel(\"measure\", measure)\n ),\n sortedColumn: definition.sortedColumn,\n };\n }\n\n onNameChanged(name) {\n this.env.model.dispatch(\"RENAME_ODOO_PIVOT\", {\n pivotId: this.props.pivotId,\n name,\n });\n }\n\n formatSort() {\n const sortedColumn = this.pivotDefinition.sortedColumn;\n const order = sortedColumn.order === \"asc\" ? _t(\"ascending\") : _t(\"descending\");\n const measureDisplayName = this.dataSource.getGroupByDisplayLabel(\n \"measure\",\n sortedColumn.measure\n );\n return `${measureDisplayName} (${order})`;\n }\n\n /**\n * Get the last update date, formatted\n *\n * @returns {string} date formatted\n */\n getLastUpdate() {\n const lastUpdate = this.dataSource.lastUpdate;\n if (lastUpdate) {\n return time_to_str(new Date(lastUpdate));\n }\n return _t(\"never\");\n }\n\n /**\n * Refresh the cache of the current pivot\n *\n */\n refresh() {\n this.env.model.dispatch(\"REFRESH_PIVOT\", { id: this.props.pivotId });\n }\n\n openDomainEdition() {\n const definition = this.env.model.getters.getPivotDefinition(this.props.pivotId);\n this.dialog.add(DomainSelectorDialog, {\n resModel: definition.model,\n initialValue: new Domain(definition.domain).toString(),\n readonly: false,\n isDebugMode: !!this.env.debug,\n onSelected: (domain) =>\n this.env.model.dispatch(\"UPDATE_ODOO_PIVOT_DOMAIN\", {\n pivotId: this.props.pivotId,\n domain: new Domain(domain).toList(),\n }),\n });\n }\n}\nPivotDetailsSidePanel.template = \"spreadsheet_edition.PivotDetailsSidePanel\";\nPivotDetailsSidePanel.components = { DomainSelector, EditableName };\nPivotDetailsSidePanel.props = {\n pivotId: {\n type: String,\n optional: true,\n },\n};\n", "/** @odoo-module */\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport PivotDetailsSidePanel from \"./pivot_details_side_panel\";\n\nconst { Component } = owl;\n\nexport default class PivotSidePanel extends Component {\n selectPivot(pivotId) {\n this.env.model.dispatch(\"SELECT_PIVOT\", { pivotId });\n }\n\n resetSelectedPivot() {\n this.env.model.dispatch(\"SELECT_PIVOT\");\n }\n\n delete(pivotId) {\n this.env.askConfirmation(_t(\"Are you sure you want to delete this pivot ?\"), () => {\n this.env.model.dispatch(\"REMOVE_PIVOT\", { pivotId });\n this.props.onCloseSidePanel();\n });\n }\n}\nPivotSidePanel.template = \"spreadsheet_edition.PivotSidePanel\";\nPivotSidePanel.components = { PivotDetailsSidePanel };\n", "/** @odoo-module */\n\nimport { _t } from \"@web/core/l10n/translation\";\nimport { Dialog } from \"@web/core/dialog/dialog\";\nimport { PivotDialogTable } from \"./spreadsheet_pivot_dialog_table\";\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nimport { makePivotFormula } from \"@spreadsheet/pivot/pivot_helpers\";\n\nconst { Component, useState } = owl;\nconst formatValue = spreadsheet.helpers.formatValue;\n\n/**\n * @typedef {Object} PivotDialogColumn\n * @property {string} formula Pivot formula\n * @property {string} value Pivot value of the formula\n * @property {number} span Size of col-span\n * @property {boolean} isMissing True if the value is missing from the sheet\n * @property {string} style Style of the column\n */\n\n/**\n * @typedef {Object} PivotDialogRow\n * @property {Array} args Args of the pivot formula\n * @property {string} formula Pivot formula\n * @property {string} value Pivot value of the formula\n * @property {boolean} isMissing True if the value is missing from the sheet\n * @property {string} style Style of the column\n */\n\n/**\n * @typedef {Object} PivotDialogValue\n * @property {Object} args\n * @property {string} args.formula Pivot formula\n * @property {string} args.value Pivot value of the formula\n * @property {boolean} isMissing True if the value is missing from the sheet\n */\n\nexport class PivotDialog extends Component {\n setup() {\n this.state = useState({\n showMissingValuesOnly: false,\n });\n this.dataSource = this.props.getters.getPivotDataSource(this.props.pivotId);\n\n const table = this.dataSource.getTableStructure();\n const id = this.props.pivotId;\n this.data = {\n columns: this._buildColHeaders(id, table),\n rows: this._buildRowHeaders(id, table),\n values: this._buildValues(id, table),\n };\n }\n\n _onCellClicked(detail) {\n this.props.insertPivotValueCallback(detail.formula);\n this.props.close();\n }\n\n // ---------------------------------------------------------------------\n // Missing values building\n // ---------------------------------------------------------------------\n\n /**\n * Retrieve the data to display in the Pivot Table\n * In the case when showMissingValuesOnly is false, the returned value\n * is the complete data\n * In the case when showMissingValuesOnly is true, the returned value is\n * the data which contains only missing values in the rows and cols. In\n * the rows, we also return the parent rows of rows which contains missing\n * values, to give context to the user.\n *\n * @returns {Object} { columns, rows, values }\n */\n getTableData() {\n if (!this.state.showMissingValuesOnly) {\n return this.data;\n }\n const colIndexes = this._getColumnsIndexes();\n const rowIndexes = this._getRowsIndexes();\n const columns = this._buildColumnsMissing(colIndexes);\n const rows = this._buildRowsMissing(rowIndexes);\n const values = this._buildValuesMissing(colIndexes, rowIndexes);\n return { columns, rows, values };\n }\n\n getRowIndex(groupValues) {\n const stringifiedValues = JSON.stringify(groupValues);\n return this._rows.findIndex((values) => JSON.stringify(values) === stringifiedValues);\n }\n\n /**\n * Retrieve the parents of the given row\n * ex:\n * Australia\n * January\n * February\n * The parent of \"January\" is \"Australia\"\n *\n * @private\n * @param {number} index Index of the row\n * @returns {Array}\n */\n _addRecursiveRow(index) {\n const rows = this.dataSource.getTableStructure().getRowHeaders();\n const row = [...rows[index].values];\n if (row.length <= 1) {\n return [index];\n }\n row.pop();\n const parentRowIndex = rows.findIndex(\n (r) => JSON.stringify(r.values) === JSON.stringify(row)\n );\n return [index].concat(this._addRecursiveRow(parentRowIndex));\n }\n /**\n * Create the columns to be used, based on the indexes of the columns in\n * which a missing value is present\n *\n * @private\n * @param {Array} indexes Indexes of columns with a missing value\n * @returns {Array>}\n */\n _buildColumnsMissing(indexes) {\n // columnsMap explode the columns in an array of array of the same\n // size with the index of each column, repeated 'span' times.\n // ex:\n // | A | B |\n // | 1 | 2 | 3 |\n // => [\n // [0, 0, 1]\n // [0, 1, 2]\n // ]\n const columnsMap = [];\n for (const column of this.data.columns) {\n const columnMap = [];\n for (const index in column) {\n for (let i = 0; i < column[index].span; i++) {\n columnMap.push(index);\n }\n }\n columnsMap.push(columnMap);\n }\n // Remove the columns that are not present in indexes\n for (let i = columnsMap[columnsMap.length - 1].length; i >= 0; i--) {\n if (!indexes.includes(i)) {\n for (const columnMap of columnsMap) {\n columnMap.splice(i, 1);\n }\n }\n }\n // Build the columns\n const columns = [];\n for (const mapIndex in columnsMap) {\n const column = [];\n let index = undefined;\n let span = 1;\n for (let i = 0; i < columnsMap[mapIndex].length; i++) {\n if (index !== columnsMap[mapIndex][i]) {\n if (index) {\n column.push(\n Object.assign({}, this.data.columns[mapIndex][index], { span })\n );\n }\n index = columnsMap[mapIndex][i];\n span = 1;\n } else {\n span++;\n }\n }\n if (index) {\n column.push(Object.assign({}, this.data.columns[mapIndex][index], { span }));\n }\n columns.push(column);\n }\n return columns;\n }\n /**\n * Create the rows to be used, based on the indexes of the rows in\n * which a missing value is present.\n *\n * @private\n * @param {Array} indexes Indexes of rows with a missing value\n * @returns {Array}\n */\n _buildRowsMissing(indexes) {\n return indexes.map((index) => this.data.rows[index]);\n }\n /**\n * Create the value to be used, based on the indexes of the columns and\n * rows in which a missing value is present.\n *\n * @private\n * @param {Array} colIndexes Indexes of columns with a missing value\n * @param {Array} rowIndexes Indexes of rows with a missing value\n * @returns {Array}\n */\n _buildValuesMissing(colIndexes, rowIndexes) {\n const values = colIndexes.map(() => []);\n for (const row of rowIndexes) {\n for (const col in colIndexes) {\n values[col].push(this.data.values[colIndexes[col]][row]);\n }\n }\n return values;\n }\n /**\n * Get the indexes of the columns in which a missing value is present\n * @private\n * @returns {Array}\n */\n _getColumnsIndexes() {\n const indexes = new Set();\n for (let i = 0; i < this.data.columns.length; i++) {\n const exploded = [];\n for (let y = 0; y < this.data.columns[i].length; y++) {\n for (let x = 0; x < this.data.columns[i][y].span; x++) {\n exploded.push(this.data.columns[i][y]);\n }\n }\n for (let y = 0; y < exploded.length; y++) {\n if (exploded[y].isMissing) {\n indexes.add(y);\n }\n }\n }\n for (let i = 0; i < this.data.columns[this.data.columns.length - 1].length; i++) {\n const values = this.data.values[i];\n if (values.find((x) => x.isMissing)) {\n indexes.add(i);\n }\n }\n return Array.from(indexes).sort((a, b) => a - b);\n }\n /**\n * Get the indexes of the rows in which a missing value is present\n * @private\n * @returns {Array}\n */\n _getRowsIndexes() {\n const rowIndexes = new Set();\n for (let i = 0; i < this.data.rows.length; i++) {\n if (this.data.rows[i].isMissing) {\n rowIndexes.add(i);\n }\n for (const col of this.data.values) {\n if (col[i].isMissing) {\n this._addRecursiveRow(i).forEach((x) => rowIndexes.add(x));\n }\n }\n }\n return Array.from(rowIndexes).sort((a, b) => a - b);\n }\n\n _getDisplayedPivotHeaderValue(domain) {\n const len = domain.length;\n if (len === 0) {\n return _t(\"Total\");\n }\n const field = domain[len - 2];\n const value = domain[len - 1];\n return this.dataSource.getGroupByDisplayLabel(field, value);\n }\n\n // ---------------------------------------------------------------------\n // Data table creation\n // ---------------------------------------------------------------------\n\n /**\n * Create the columns headers of the Pivot\n *\n * @param {string} id Pivot Id\n * @param {SpreadsheetPivotTable} table\n *\n * @private\n * @returns {Array>}\n */\n _buildColHeaders(id, table) {\n const headers = [];\n for (const row of table.getColHeaders()) {\n const current = [];\n for (const cell of row) {\n const domain = [];\n for (let i = 0; i < cell.fields.length; i++) {\n domain.push(cell.fields[i]);\n domain.push(cell.values[i]);\n }\n current.push({\n formula: makePivotFormula(\"ODOO.PIVOT.HEADER\", [id, ...domain]),\n value: this._getDisplayedPivotHeaderValue(domain),\n span: cell.width,\n isMissing: !this.dataSource.isUsedHeader(domain),\n });\n }\n headers.push(current);\n }\n const last = headers[headers.length - 1];\n headers[headers.length - 1] = last.map((cell) => {\n if (!cell.isMissing) {\n cell.style = \"color: #756f6f;\";\n }\n return cell;\n });\n return headers;\n }\n /**\n * Create the row of the pivot table\n *\n * @param {string} id Pivot Id\n * @param {SpreadsheetPivotTable} table\n *\n * @private\n * @returns {Array}\n */\n _buildRowHeaders(id, table) {\n const headers = [];\n for (const row of table.getRowHeaders()) {\n const domain = [];\n for (let i = 0; i < row.fields.length; i++) {\n domain.push(row.fields[i]);\n domain.push(row.values[i]);\n }\n const cell = {\n args: domain,\n formula: makePivotFormula(\"ODOO.PIVOT.HEADER\", [id, ...domain]),\n value: this._getDisplayedPivotHeaderValue(domain),\n isMissing: !this.dataSource.isUsedHeader(domain),\n };\n if (row.indent > 1) {\n cell.style = `padding-left: ${row.indent - 1 * 10}px`;\n }\n headers.push(cell);\n }\n return headers;\n }\n /**\n * Build the values of the pivot table\n *\n * @param {string} id Pivot Id\n * @param {SpreadsheetPivotTable} table\n *\n * @private\n * @returns {Array}\n */\n _buildValues(id, table) {\n const values = [];\n for (const col of table.getMeasureHeaders()) {\n const current = [];\n const measure = col.values[col.values.length - 1];\n for (const row of table.getRowHeaders()) {\n const domain = [];\n for (let i = 0; i < row.fields.length; i++) {\n domain.push(row.fields[i]);\n domain.push(row.values[i]);\n }\n for (let i = 0; i < col.fields.length - 1; i++) {\n domain.push(col.fields[i]);\n domain.push(col.values[i]);\n }\n const value = this.dataSource.getPivotCellValue(measure, domain);\n current.push({\n args: {\n formula: makePivotFormula(\"ODOO.PIVOT\", [id, measure, ...domain]),\n value: !value ? \"\" : formatValue(value),\n },\n isMissing: !this.dataSource.isUsedValue(domain, measure),\n });\n }\n values.push(current);\n }\n return values;\n }\n}\n\nPivotDialog.template = \"spreadsheet_edition.PivotDialog\";\nPivotDialog.components = { Dialog, PivotDialogTable };\n", "/** @odoo-module */\nconst { Component } = owl;\n\nexport class PivotDialogTable extends Component {\n _onCellClicked(formula) {\n this.props.onCellSelected({ formula });\n }\n}\nPivotDialogTable.template = \"spreadsheet_edition.PivotDialogTable\";\n", "/** @odoo-module **/\nimport { registry } from \"@web/core/registry\";\nimport { download } from \"@web/core/network/download\";\nimport { useService } from \"@web/core/utils/hooks\";\n\nimport SpreadsheetComponent from \"@spreadsheet_edition/bundle/actions/spreadsheet_component\";\nimport { SpreadsheetName } from \"@spreadsheet_edition/bundle/actions/control_panel/spreadsheet_name\";\n\nimport { UNTITLED_SPREADSHEET_NAME } from \"@spreadsheet/helpers/constants\";\nimport { convertFromSpreadsheetTemplate } from \"@documents_spreadsheet/bundle/helpers\";\nimport { AbstractSpreadsheetAction } from \"@spreadsheet_edition/bundle/actions/abstract_spreadsheet_action\";\nimport { DocumentsSpreadsheetControlPanel } from \"../components/control_panel/spreadsheet_control_panel\";\nimport { RecordFileStore } from \"@spreadsheet_edition/bundle/image/record_file_store\";\n\nconst { Component, useState } = owl;\n\nexport class SpreadsheetAction extends AbstractSpreadsheetAction {\n setup() {\n super.setup();\n this.orm = useService(\"orm\");\n this.actionService = useService(\"action\");\n this.notificationMessage = this.env._t(\"New spreadsheet created in Documents\");\n\n this.state = useState({\n numberOfConnectedUsers: 1,\n isSynced: true,\n isFavorited: false,\n spreadsheetName: UNTITLED_SPREADSHEET_NAME,\n });\n\n this.spreadsheetCollaborative = useService(\"spreadsheet_collaborative\");\n this.fileStore = new RecordFileStore(\"documents.document\", this.resId, this.http, this.orm);\n }\n\n async onWillStart() {\n await super.onWillStart();\n this.transportService = this.spreadsheetCollaborative.getCollaborativeChannel(\n Component.env,\n \"documents.document\",\n this.resId\n );\n }\n\n async _fetchData() {\n const record = await this.orm.call(\"documents.document\", \"join_spreadsheet_session\", [\n this.resId,\n ]);\n if (this.params.convert_from_template) {\n return {\n ...record,\n raw: await convertFromSpreadsheetTemplate(this.orm, record.raw),\n };\n }\n return record;\n }\n\n /**\n * @override\n */\n _initializeWith(record) {\n this.state.isFavorited = record.is_favorited;\n this.spreadsheetData = record.raw;\n this.stateUpdateMessages = record.revisions;\n this.snapshotRequested = record.snapshot_requested;\n this.state.spreadsheetName = record.name;\n this.isReadonly = record.isReadonly;\n }\n\n /**\n * @private\n * @param {Object}\n */\n async _onDownload({ name, files }) {\n await download({\n url: \"/spreadsheet/xlsx\",\n data: {\n zip_name: `${name}.xlsx`,\n files: JSON.stringify(files),\n },\n });\n }\n\n /**\n * @param {OdooEvent} ev\n * @returns {Promise}\n */\n async _onSpreadSheetFavoriteToggled(ev) {\n this.state.isFavorited = !this.state.isFavorited;\n return await this.orm.call(\"documents.document\", \"toggle_favorited\", [[this.resId]]);\n }\n\n /**\n * Updates the control panel with the sync status of spreadsheet\n *\n * @param {Object}\n */\n _onSpreadsheetSyncStatus({ synced, numberOfConnectedUsers }) {\n this.state.isSynced = synced;\n this.state.numberOfConnectedUsers = numberOfConnectedUsers;\n }\n\n /**\n * Reload the spreadsheet if an unexpected revision id is triggered.\n */\n _onUnexpectedRevisionId() {\n this.actionService.doAction(\"reload_context\");\n }\n\n /**\n * Create a copy of the given spreadsheet and display it\n */\n async _onMakeCopy({ data, thumbnail }) {\n const defaultValues = {\n mimetype: \"application/o-spreadsheet\",\n raw: JSON.stringify(data),\n spreadsheet_snapshot: false,\n thumbnail,\n };\n const id = await this.orm.call(\"documents.document\", \"copy\", [this.resId], {\n default: defaultValues,\n });\n this._openSpreadsheet(id);\n }\n\n /**\n * Create a new sheet and display it\n */\n async _onNewSpreadsheet() {\n const action = await this.orm.call(\"documents.document\", \"action_open_new_spreadsheet\");\n this._notifyCreation();\n this.actionService.doAction(action, { clear_breadcrumbs: true });\n }\n\n async _onSpreadsheetSaved({ thumbnail }) {\n await this.orm.write(\"documents.document\", [this.resId], { thumbnail });\n }\n\n /**\n * Saves the spreadsheet name change.\n * @param {Object} detail\n * @returns {Promise}\n */\n async _onSpreadSheetNameChanged(detail) {\n const { name } = detail;\n this.state.spreadsheetName = name;\n this.env.config.setDisplayName(this.state.spreadsheetName);\n return await this.orm.write(\"documents.document\", [this.resId], { name });\n }\n}\n\nSpreadsheetAction.template = \"documents_spreadsheet.SpreadsheetAction\";\nSpreadsheetAction.components = {\n SpreadsheetComponent,\n DocumentsSpreadsheetControlPanel,\n SpreadsheetName,\n};\n\nregistry.category(\"actions\").add(\"action_open_spreadsheet\", SpreadsheetAction, { force: true });\n", "/** @odoo-module **/\nimport { registry } from \"@web/core/registry\";\nimport { _t } from \"web.core\";\n\nimport SpreadsheetComponent from \"@spreadsheet_edition/bundle/actions/spreadsheet_component\";\nimport { base64ToJson, jsonToBase64 } from \"@spreadsheet_edition/bundle/helpers\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { AbstractSpreadsheetAction } from \"@spreadsheet_edition/bundle/actions/abstract_spreadsheet_action\";\nimport { DocumentsSpreadsheetControlPanel } from \"@documents_spreadsheet/bundle/components/control_panel/spreadsheet_control_panel\";\n\nexport class SpreadsheetTemplateAction extends AbstractSpreadsheetAction {\n setup() {\n super.setup();\n this.notificationMessage = this.env._t(\"New spreadsheet template created\");\n this.orm = useService(\"orm\");\n }\n\n _initializeWith(record) {\n this.spreadsheetData = base64ToJson(record.data);\n this.state.spreadsheetName = record.name;\n this.isReadonly = record.isReadonly;\n }\n\n /**\n * Fetch all the necessary data to open a spreadsheet template\n * @returns {Object}\n */\n async _fetchData() {\n return this.orm.call(\"spreadsheet.template\", \"fetch_template_data\", [this.resId]);\n }\n\n /**\n * Create a new empty spreadsheet template\n * @returns {number} id of the newly created spreadsheet template\n */\n async _onNewSpreadsheet() {\n const data = {\n name: _t(\"Untitled spreadsheet template\"),\n data: btoa(\"{}\"),\n };\n const id = await this.orm.create(\"spreadsheet.template\", [data]);\n this._openSpreadsheet(id);\n return id;\n }\n\n /**\n * Save the data and thumbnail on the given template\n * @param {number} spreadsheetTemplateId\n * @param {Object} values values to save\n * @param {Object} values.data exported spreadsheet data\n * @param {string} values.thumbnail spreadsheet thumbnail\n */\n async _onSpreadsheetSaved({ data, thumbnail }) {\n await this.orm.write(\"spreadsheet.template\", [this.resId], {\n data: jsonToBase64(data),\n thumbnail,\n });\n }\n\n /**\n * Save a new name for the given template\n * @param {Object} detail\n * @param {string} detail.name\n */\n async _onSpreadSheetNameChanged(detail) {\n const { name } = detail;\n this.state.spreadsheetName = name;\n this.env.config.setDisplayName(this.state.spreadsheetName);\n await this.orm.write(\"spreadsheet.template\", [this.resId], {\n name,\n });\n }\n\n async _onMakeCopy({ data, thumbnail }) {\n const defaultValues = {\n data: jsonToBase64(data),\n thumbnail,\n };\n const id = await this.orm.call(\"spreadsheet.template\", \"copy\", [this.resId], {\n default: defaultValues,\n });\n this._openSpreadsheet(id);\n }\n}\n\nSpreadsheetTemplateAction.template = \"documents_spreadsheet.SpreadsheetTemplateAction\";\nSpreadsheetTemplateAction.components = {\n SpreadsheetComponent,\n DocumentsSpreadsheetControlPanel,\n};\n\nregistry\n .category(\"actions\")\n .add(\"action_open_template\", SpreadsheetTemplateAction, { force: true });\n", "/** @odoo-module **/\n\nimport { SpreadsheetControlPanel } from \"@spreadsheet_edition/bundle/actions/control_panel/spreadsheet_control_panel\";\n\n\nexport class DocumentsSpreadsheetControlPanel extends SpreadsheetControlPanel {}\n\nDocumentsSpreadsheetControlPanel.template =\n \"documents_spreadsheet.DocumentsSpreadsheetControlPanel\";\nDocumentsSpreadsheetControlPanel.components = {\n ...SpreadsheetControlPanel.components,\n};\nDocumentsSpreadsheetControlPanel.props = {\n ...SpreadsheetControlPanel.props,\n isFavorited: {\n type: Boolean,\n optional: true,\n },\n onFavoriteToggled: {\n type: Function,\n optional: true,\n },\n};\n", "/** @odoo-module */\n\nimport { jsonToBase64 } from \"@spreadsheet_edition/bundle/helpers\";\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport SpreadsheetComponent from \"@spreadsheet_edition/bundle/actions/spreadsheet_component\";\nimport { _t } from \"@web/core/l10n/translation\";\nimport { patch } from \"@web/core/utils/patch\";\nimport { sprintf } from \"@web/core/utils/strings\";\n\nconst { Model } = spreadsheet;\n\nconst { useSubEnv } = owl;\n\npatch(SpreadsheetComponent.prototype, \"documents_spreadsheet.SpreadsheetComponent\", {\n setup() {\n this._super();\n useSubEnv({\n saveAsTemplate: this._saveAsTemplate.bind(this),\n });\n },\n\n /**\n * @private\n * @returns {Promise}\n */\n async _saveAsTemplate() {\n const model = new Model(this.model.exportData(), {\n custom: {\n env: this.env,\n dataSources: this.model.config.custom.dataSources,\n },\n });\n await model.config.custom.dataSources.waitForAllLoaded();\n const proms = [];\n for (const pivotId of model.getters.getPivotIds()) {\n proms.push(model.getters.getPivotDataSource(pivotId).prepareForTemplateGeneration());\n }\n await Promise.all(proms);\n model.dispatch(\"CONVERT_PIVOT_TO_TEMPLATE\");\n const data = model.exportData();\n const name = this.props.name;\n this.trigger(\"do-action\", {\n action: \"documents_spreadsheet.save_spreadsheet_template_action\",\n options: {\n additional_context: {\n default_template_name: sprintf(_t(\"%s - Template\"), name),\n default_data: jsonToBase64(data),\n default_thumbnail: this.getThumbnail(),\n },\n },\n });\n },\n});\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { DataSources } from \"@spreadsheet/data_sources/data_sources\";\nimport { migrate } from \"@spreadsheet/o_spreadsheet/migration\";\n\nconst Model = spreadsheet.Model;\n\n/**\n * Convert PIVOT functions from relative to absolute.\n *\n * @param {object} orm\n * @param {object} data\n * @returns {Promise} spreadsheetData\n */\nexport async function convertFromSpreadsheetTemplate(orm, data) {\n const model = new Model(migrate(data), {\n custom: { dataSources: new DataSources(orm) },\n });\n await model.config.custom.dataSources.waitForAllLoaded();\n const proms = [];\n for (const pivotId of model.getters.getPivotIds()) {\n proms.push(model.getters.getPivotDataSource(pivotId).prepareForTemplateGeneration());\n }\n await Promise.all(proms);\n model.dispatch(\"CONVERT_PIVOT_FROM_TEMPLATE\");\n return model.exportData();\n}\n", "/** @odoo-module */\n\nimport PivotDataSource from \"@spreadsheet/pivot/pivot_data_source\";\nimport { patch } from \"@web/core/utils/patch\";\n\npatch(PivotDataSource.prototype, \"documents_spreadsheet_templates_data_source\", {\n /**\n * @param {string} fieldName\n */\n getPossibleValuesForGroupBy(fieldName) {\n this._assertDataIsLoaded();\n return this._model.getPossibleValuesForGroupBy(fieldName);\n },\n});\n", "/** @odoo-module */\n\nimport { SpreadsheetPivotModel } from \"@spreadsheet/pivot/pivot_model\";\nimport { patch } from \"@web/core/utils/patch\";\nimport { Domain } from \"@web/core/domain\";\n\npatch(SpreadsheetPivotModel.prototype, \"documents_spreadsheet_templates_pivot_model\", {\n setup() {\n this._super.apply(this, arguments);\n /**\n * Contains the possible values for each group by of the pivot. This attribute is used *only* for templates,\n * so it's computed only in prepareForTemplateGeneration\n */\n this._fieldsValue = {};\n },\n\n /**\n * Get the possible values for the given groupBy\n * @param {string} groupBy\n * @returns {any[]}\n */\n getPossibleValuesForGroupBy(groupBy) {\n return this._fieldsValue[groupBy] || [];\n },\n\n /**\n * This method is used to compute the possible values for each group bys.\n * It should be run before using templates\n */\n async prepareForTemplateGeneration() {\n const colValues = [];\n const rowValues = [];\n\n function collectValues(tree, collector) {\n const group = tree.root;\n if (!tree.directSubTrees.size) {\n //It's a leaf, we can fill the cols\n collector.push([...group.values]);\n }\n [...tree.directSubTrees.values()].forEach((subTree) => {\n collectValues(subTree, collector);\n });\n }\n\n collectValues(this.data.colGroupTree, colValues);\n collectValues(this.data.rowGroupTree, rowValues);\n\n for (let i = 0; i < this.metaData.fullRowGroupBys.length; i++) {\n let vals = [\n ...new Set(rowValues.map((array) => array[i]).filter((val) => val !== undefined)),\n ];\n if (i !== 0) {\n vals = await this._orderValues(vals, this.metaData.fullRowGroupBys[i]);\n }\n this._fieldsValue[this.metaData.fullRowGroupBys[i]] = vals;\n }\n for (let i = 0; i < this.metaData.fullColGroupBys.length; i++) {\n let vals = [];\n if (i !== 0) {\n vals = await this._orderValues(vals, this.metaData.fullColGroupBys[i]);\n } else {\n vals = colValues.map((array) => array[i]).filter((val) => val !== undefined);\n vals = [...new Set(vals)];\n }\n this._fieldsValue[this.metaData.fullColGroupBys[i]] = vals;\n }\n },\n\n /**\n * Order the given values for the given groupBy. This is done by executing a\n * search_read\n */\n async _orderValues(values, groupBy) {\n const field = this.parseGroupField(groupBy).field;\n const model = this.metaData.resModel;\n const context = this.searchParams.context;\n const baseDomain = this.searchParams.domain;\n const requestField = field.relation ? \"id\" : field.name;\n const domain = Domain.and([\n field.relation ? [] : baseDomain,\n [[requestField, \"in\", values]],\n ]).toList();\n // orderby is omitted for relational fields on purpose to have the default order of the model\n const records = await this.orm.searchRead(\n field.relation ? field.relation : model,\n domain,\n [requestField],\n {\n order: field.relation ? undefined : `${field.name} ASC`,\n context: { ...context, active_test: false },\n }\n );\n return [...new Set(records.map((record) => record[requestField].toString()))];\n },\n});\n", "odoo.define(\"documents_spreadsheet.PivotTemplatePlugin\", function (require) {\n (\"use strict\");\n\n const spreadsheet = require(\"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\")[\n Symbol.for(\"default\")\n ];\n const CommandResult = require(\"@spreadsheet/o_spreadsheet/cancelled_reason\")[\n Symbol.for(\"default\")\n ];\n const { pivotFormulaRegex } = require(\"@spreadsheet/pivot/pivot_helpers\");\n const { parse, astToFormula } = spreadsheet;\n const { featurePluginRegistry } = spreadsheet.registries;\n\n /**\n * @typedef {Object} Range\n */\n\n class PivotTemplatePlugin extends spreadsheet.UIPlugin {\n allowDispatch(cmd) {\n switch (cmd.type) {\n case \"CONVERT_PIVOT_TO_TEMPLATE\":\n case \"CONVERT_PIVOT_FROM_TEMPLATE\": {\n for (const pivotId of this.getters.getPivotIds()) {\n if (!this.getters.getPivotDataSource(pivotId).isReady()) {\n return CommandResult.PivotCacheNotLoaded;\n }\n }\n break;\n }\n }\n return CommandResult.Success;\n }\n\n /**\n * Handle a spreadsheet command\n *\n * @param {Object} cmd Command\n */\n handle(cmd) {\n switch (cmd.type) {\n case \"CONVERT_PIVOT_TO_TEMPLATE\":\n this._convertFormulas(\n this._getCells(pivotFormulaRegex),\n this._absoluteToRelative.bind(this),\n this.getters.getPivotIds().map(this.getters.getPivotDefinition)\n );\n break;\n case \"CONVERT_PIVOT_FROM_TEMPLATE\":\n this._convertFormulas(\n this._getCells(pivotFormulaRegex),\n this._relativeToAbsolute.bind(this),\n this.getters.getPivotIds().map(this.getters.getPivotDefinition)\n );\n this._removeInvalidPivotRows();\n break;\n }\n }\n\n /**\n * Applies a transformation function to all given formula cells.\n * The transformation function takes as fist parameter the cell AST and should\n * return a modified AST.\n * Any additional parameter is forwarded to the transformation function.\n *\n * @param {Array} cells\n * @param {Function} convertFunction\n * @param {...any} args\n */\n _convertFormulas(cells, convertFunction, ...args) {\n cells.forEach((cell) => {\n if (cell.isFormula) {\n const { col, row, sheetId } = this.getters.getCellPosition(cell.id);\n const ast = convertFunction(parse(cell.content), ...args);\n if (ast) {\n const content = `=${astToFormula(ast)}`;\n this.dispatch(\"UPDATE_CELL\", {\n content,\n sheetId,\n col,\n row,\n });\n } else {\n this.dispatch(\"CLEAR_CELL\", {\n sheetId,\n col,\n row,\n });\n }\n }\n });\n }\n\n /**\n * Return all formula cells matching a given regular expression.\n *\n * @param {RegExp} regex\n * @returns {Array}\n */\n _getCells(regex) {\n return this.getters\n .getSheetIds()\n .map((sheetId) =>\n Object.values(this.getters.getCells(sheetId)).filter(\n (cell) =>\n cell.isFormula &&\n regex.test(this.getters.getFormulaCellContent(sheetId, cell))\n )\n )\n .flat();\n }\n\n /**\n * return AST from an relative PIVOT ast to a absolute PIVOT ast (sheet -> template)\n * *\n * relative PIVOTS formulas use the position while Absolute PIVOT\n * formulas use hardcoded ids\n *\n * e.g.\n * The following relative formula\n * `PIVOT(\"1\",\"probability\",\"product_id\",PIVOT.POSITION(\"1\",\"product_id\",0),\"bar\",\"110\")`\n * is converted to\n * `PIVOT(\"1\",\"probability\",\"product_id\",\"37\",\"bar\",\"110\")`\n *\n * @param {Object} ast\n * @returns {Object}\n */\n _relativeToAbsolute(ast) {\n switch (ast.type) {\n case \"FUNCALL\":\n switch (ast.value) {\n case \"ODOO.PIVOT.POSITION\":\n return this._pivotPositionToAbsolute(ast);\n default:\n return Object.assign({}, ast, {\n args: ast.args.map((child) => this._relativeToAbsolute(child)),\n });\n }\n case \"UNARY_OPERATION\":\n return Object.assign({}, ast, {\n operand: this._relativeToAbsolute(ast.operand),\n });\n case \"BIN_OPERATION\":\n return Object.assign({}, ast, {\n right: this._relativeToAbsolute(ast.right),\n left: this._relativeToAbsolute(ast.left),\n });\n }\n return ast;\n }\n\n /**\n * return AST from an absolute PIVOT ast to a relative ast.\n *\n * Absolute PIVOT formulas use hardcoded ids while relative PIVOTS\n * formulas use the position\n *\n * e.g.\n * The following absolute formula\n * `PIVOT(\"1\",\"probability\",\"product_id\",\"37\",\"bar\",\"110\")`\n * is converted to\n * `PIVOT(\"1\",\"probability\",\"product_id\",PIVOT.POSITION(\"1\",\"product_id\",0),\"bar\",\"110\")`\n *\n * @param {Object} ast\n * @returns {Object}\n */\n _absoluteToRelative(ast) {\n switch (ast.type) {\n case \"FUNCALL\":\n switch (ast.value) {\n case \"ODOO.PIVOT\":\n return this._pivot_absoluteToRelative(ast);\n case \"ODOO.PIVOT.HEADER\":\n return this._pivotHeader_absoluteToRelative(ast);\n default:\n return Object.assign({}, ast, {\n args: ast.args.map((child) => this._absoluteToRelative(child)),\n });\n }\n case \"UNARY_OPERATION\":\n return Object.assign({}, ast, {\n operand: this._absoluteToRelative(ast.operand),\n });\n case \"BIN_OPERATION\":\n return Object.assign({}, ast, {\n right: this._absoluteToRelative(ast.right),\n left: this._absoluteToRelative(ast.left),\n });\n }\n return ast;\n }\n\n /**\n * Convert a PIVOT.POSITION function AST to an absolute AST\n *\n * @see _relativeToAbsolute\n * @param {Object} ast\n * @returns {Object}\n */\n _pivotPositionToAbsolute(ast) {\n const [pivotIdAst, fieldAst, positionAst] = ast.args;\n const pivotId = pivotIdAst.value;\n const fieldName = fieldAst.value;\n const position = positionAst.value;\n const values = this.getters.getPivotGroupByValues(pivotId, fieldName);\n const id = values[position - 1];\n return {\n value: id ? `${id}` : `\"#IDNOTFOUND\"`,\n type: id ? \"STRING\" : \"UNKNOWN\",\n };\n }\n /**\n * Convert an absolute PIVOT.HEADER function AST to a relative AST\n *\n * @see _absoluteToRelative\n * @param {Object} ast\n * @returns {Object}\n */\n _pivotHeader_absoluteToRelative(ast) {\n ast = Object.assign({}, ast);\n const [pivotIdAst, ...domainAsts] = ast.args;\n if (pivotIdAst.type !== \"STRING\" && pivotIdAst.type !== \"NUMBER\") {\n return ast;\n }\n ast.args = [pivotIdAst, ...this._domainToRelative(pivotIdAst, domainAsts)];\n return ast;\n }\n /**\n * Convert an absolute PIVOT function AST to a relative AST\n *\n * @see _absoluteToRelative\n * @param {Object} ast\n * @returns {Object}\n */\n _pivot_absoluteToRelative(ast) {\n ast = Object.assign({}, ast);\n const [pivotIdAst, measureAst, ...domainAsts] = ast.args;\n if (pivotIdAst.type !== \"STRING\" && pivotIdAst.type !== \"NUMBER\") {\n return ast;\n }\n ast.args = [pivotIdAst, measureAst, ...this._domainToRelative(pivotIdAst, domainAsts)];\n return ast;\n }\n\n /**\n * Convert a pivot domain with hardcoded ids to a relative\n * domain with positions instead. Each domain element is\n * represented as an AST.\n *\n * e.g. (ignoring AST representation for simplicity)\n * The following domain\n * \"product_id\", \"37\", \"stage_id\", \"4\"\n * is converted to\n * \"product_id\", PIVOT.POSITION(\"#pivotId\", \"product_id\", 15), \"stage_id\", PIVOT.POSITION(\"#pivotId\", \"stage_id\", 3)\n *\n * @param {Object} pivotIdAst\n * @param {Object} domainAsts\n * @returns {Array}\n */\n _domainToRelative(pivotIdAst, domainAsts) {\n let relativeDomain = [];\n for (let i = 0; i <= domainAsts.length - 1; i += 2) {\n const fieldAst = domainAsts[i];\n const valueAst = domainAsts[i + 1];\n const pivotId = pivotIdAst.value;\n const fieldName = fieldAst.value;\n if (\n this._isAbsolute(pivotId, fieldName) &&\n fieldAst.type === \"STRING\" &&\n [\"STRING\", \"NUMBER\"].includes(valueAst.type)\n ) {\n const id = valueAst.value;\n const values = this.getters.getPivotGroupByValues(pivotId, fieldName);\n const index = values.map((val) => val.toString()).indexOf(id.toString());\n relativeDomain = relativeDomain.concat([\n fieldAst,\n {\n type: \"FUNCALL\",\n value: \"ODOO.PIVOT.POSITION\",\n args: [pivotIdAst, fieldAst, { type: \"NUMBER\", value: index + 1 }],\n },\n ]);\n } else {\n relativeDomain = relativeDomain.concat([fieldAst, valueAst]);\n }\n }\n return relativeDomain;\n }\n\n _isAbsolute(pivotId, fieldName) {\n const field = this.getters\n .getPivotDataSource(pivotId)\n .getField(fieldName.split(\":\")[0]);\n return field && field.type === \"many2one\";\n }\n\n /**\n * Remove pivot formulas with invalid ids.\n * i.e. pivot formulas containing \"#IDNOTFOUND\".\n *\n * Rows where all pivot formulas are invalid are removed, even\n * if there are others non-empty cells.\n * Invalid formulas next to valid ones (in the same row) are simply removed.\n */\n _removeInvalidPivotRows() {\n for (const sheetId of this.getters.getSheetIds()) {\n const invalidRows = [];\n\n for (let rowIndex = 0; rowIndex < this.getters.getNumberRows(sheetId); rowIndex++) {\n const cellIds = Object.values(this.getters.getRowCells(sheetId, rowIndex));\n const [valid, invalid] = cellIds\n .map((id) => this.getters.getCellById(id))\n .filter((cell) => cell.isFormula && /^\\s*=.*PIVOT/.test(cell.content))\n .reduce(\n ([valid, invalid], cell) => {\n const isInvalid = /^\\s*=.*PIVOT(\\.HEADER)?.*#IDNOTFOUND/.test(\n cell.content\n );\n return [\n isInvalid ? valid : valid + 1,\n isInvalid ? invalid + 1 : invalid,\n ];\n },\n [0, 0]\n );\n if (invalid > 0 && valid === 0) {\n invalidRows.push(rowIndex);\n }\n }\n this.dispatch(\"REMOVE_COLUMNS_ROWS\", {\n dimension: \"ROW\",\n elements: invalidRows,\n sheetId,\n });\n }\n this._convertFormulas(this._getCells(/^\\s*=.*PIVOT.*#IDNOTFOUND/), () => null);\n }\n }\n\n PivotTemplatePlugin.getters = [];\n\n featurePluginRegistry.add(\"PivotTemplate\", PivotTemplatePlugin);\n\n return PivotTemplatePlugin;\n});\n", "/** @odoo-module */\n\nimport { registry } from \"@web/core/registry\";\nimport { ControlPanel } from \"@web/search/control_panel/control_panel\";\nimport { DashboardLoader, Status } from \"./dashboard_loader\";\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { useSetupAction } from \"@web/webclient/actions/action_hook\";\nimport { DashboardMobileSearchPanel } from \"./mobile_search_panel/mobile_search_panel\";\nimport { MobileFigureContainer } from \"./mobile_figure_container/mobile_figure_container\";\nimport { FilterValue } from \"@spreadsheet/global_filters/components/filter_value/filter_value\";\nimport { loadSpreadsheetDependencies } from \"@spreadsheet/helpers/helpers\";\nimport { useService } from \"@web/core/utils/hooks\";\n\nconst { Spreadsheet } = spreadsheet;\nconst { Component, onWillStart, useState, useEffect } = owl;\n\nexport class SpreadsheetDashboardAction extends Component {\n setup() {\n this.Status = Status;\n this.controlPanelDisplay = {\n \"top-left\": true,\n \"top-right\": true,\n \"bottom-left\": false,\n \"bottom-right\": false,\n };\n this.orm = useService(\"orm\");\n this.router = useService(\"router\");\n // Use the non-protected orm service (`this.env.services.orm` instead of `useService(\"orm\")`)\n // because spreadsheets models are preserved across multiple components when navigating\n // with the breadcrumb\n // TODO write a test\n /** @type {DashboardLoader}*/\n this.loader = useState(\n new DashboardLoader(this.env, this.env.services.orm, this._fetchDashboardData)\n );\n onWillStart(async () => {\n await loadSpreadsheetDependencies();\n if (this.props.state && this.props.state.dashboardLoader) {\n const { groups, dashboards } = this.props.state.dashboardLoader;\n this.loader.restoreFromState(groups, dashboards);\n } else {\n await this.loader.load();\n }\n const activeDashboardId = this.getInitialActiveDashboard();\n if (activeDashboardId) {\n this.openDashboard(activeDashboardId);\n }\n });\n useEffect(\n () => this.router.pushState({ dashboard_id: this.activeDashboardId }),\n () => [this.activeDashboardId]\n );\n useEffect(\n () => {\n const dashboard = this.state.activeDashboard;\n if (dashboard && dashboard.status === Status.Loaded) {\n const render = () => this.render(true);\n dashboard.model.on(\"update\", this, render);\n return () => dashboard.model.off(\"update\", this, render);\n }\n },\n () => {\n const dashboard = this.state.activeDashboard;\n return [dashboard && dashboard.model, dashboard && dashboard.status];\n }\n );\n useSetupAction({\n getLocalState: () => {\n return {\n activeDashboardId: this.activeDashboardId,\n dashboardLoader: this.loader.getState(),\n };\n },\n });\n /** @type {{ activeDashboard: import(\"./dashboard_loader\").Dashboard}} */\n this.state = useState({ activeDashboard: undefined });\n }\n\n /**\n * @returns {number | undefined}\n */\n get activeDashboardId() {\n return this.state.activeDashboard ? this.state.activeDashboard.id : undefined;\n }\n\n /**\n * @returns {object[]}\n */\n get filters() {\n const dashboard = this.state.activeDashboard;\n if (!dashboard || dashboard.status !== Status.Loaded) {\n return [];\n }\n return dashboard.model.getters.getGlobalFilters();\n }\n\n /**\n * @private\n * @returns {number | undefined}\n */\n getInitialActiveDashboard() {\n if (this.props.state && this.props.state.activeDashboardId) {\n return this.props.state.activeDashboardId;\n }\n const params = this.props.action.params || this.props.action.context.params;\n if (params && params.dashboard_id) {\n return params.dashboard_id;\n }\n const [firstSection] = this.getDashboardGroups();\n if (firstSection && firstSection.dashboards.length) {\n return firstSection.dashboards[0].id;\n }\n }\n\n getDashboardGroups() {\n return this.loader.getDashboardGroups();\n }\n\n /**\n * @param {number} dashboardId\n */\n openDashboard(dashboardId) {\n this.state.activeDashboard = this.loader.getDashboard(dashboardId);\n }\n\n /**\n * @private\n * @param {number} dashboardId\n * @returns {Promise<{ data: string, revisions: object[] }>}\n */\n async _fetchDashboardData(dashboardId) {\n const [record] = await this.orm.read(\"spreadsheet.dashboard\", [dashboardId], [\"raw\"]);\n return { data: JSON.parse(record.raw), revisions: [] };\n }\n}\nSpreadsheetDashboardAction.template = \"spreadsheet_dashboard.DashboardAction\";\nSpreadsheetDashboardAction.components = {\n ControlPanel,\n Spreadsheet,\n FilterValue,\n DashboardMobileSearchPanel,\n MobileFigureContainer,\n};\n\nregistry\n .category(\"actions\")\n .add(\"action_spreadsheet_dashboard\", SpreadsheetDashboardAction, { force: true });\n", "/** @odoo-module */\n\nimport { DataSources } from \"@spreadsheet/data_sources/data_sources\";\nimport { migrate } from \"@spreadsheet/o_spreadsheet/migration\";\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nconst { Model } = spreadsheet;\n\n/**\n * @type {{\n * NotLoaded: \"NotLoaded\",\n * Loading: \"Loading\",\n * Loaded: \"Loaded\",\n * Error: \"Error\",\n * }}\n */\nexport const Status = {\n NotLoaded: \"NotLoaded\",\n Loading: \"Loading\",\n Loaded: \"Loaded\",\n Error: \"Error\",\n};\n\n/**\n * @typedef Dashboard\n * @property {number} id\n * @property {string} displayName\n * @property {string} status\n * @property {Model} [model]\n * @property {Error} [error]\n *\n * @typedef DashboardGroupData\n * @property {number} id\n * @property {string} name\n * @property {Array} dashboardIds\n *\n * @typedef DashboardGroup\n * @property {number} id\n * @property {string} name\n * @property {Array} dashboards\n *\n * @typedef {(dashboardId: number) => Promise<{ data: string, revisions: object[] }>} FetchDashboardData\n *\n * @typedef {import(\"@web/env\").OdooEnv} OdooEnv\n *\n * @typedef {import(\"@web/core/orm_service\").ORM} ORM\n */\n\nexport class DashboardLoader {\n /**\n * @param {OdooEnv} env\n * @param {ORM} orm\n * @param {FetchDashboardData} fetchDashboardData\n */\n constructor(env, orm, fetchDashboardData) {\n /** @private */\n this.env = env;\n /** @private */\n this.orm = orm;\n /** @private @type {Array} */\n this.groups = [];\n /** @private @type {Object} */\n this.dashboards = {};\n /** @private */\n this.fetchDashboardData = fetchDashboardData;\n }\n\n /**\n * @param {Array} groups\n * @param {Object} dashboards\n */\n restoreFromState(groups, dashboards) {\n this.groups = groups;\n this.dashboards = dashboards;\n }\n\n /**\n * Return data needed to restore a dashboard loader\n */\n getState() {\n return {\n groups: this.groups,\n dashboards: this.dashboards,\n };\n }\n\n async load() {\n const groups = await this._fetchGroups();\n this.groups = groups\n .filter((group) => group.dashboard_ids.length)\n .map((group) => ({\n id: group.id,\n name: group.name,\n dashboardIds: group.dashboard_ids,\n }));\n const dashboards = await this._fetchDashboardNames(this.groups);\n for (const dashboard of dashboards) {\n this.dashboards[dashboard.id] = {\n id: dashboard.id,\n displayName: dashboard.name,\n status: Status.NotLoaded,\n };\n }\n }\n\n /**\n * @param {number} dashboardId\n * @returns {Dashboard}\n */\n getDashboard(dashboardId) {\n const dashboard = this._getDashboard(dashboardId);\n if (dashboard.status === Status.NotLoaded) {\n this._loadDashboardData(dashboardId);\n }\n return dashboard;\n }\n\n /**\n * @returns {Array}\n */\n getDashboardGroups() {\n return this.groups.map((section) => ({\n id: section.id,\n name: section.name,\n dashboards: section.dashboardIds.map((dashboardId) => ({\n id: dashboardId,\n displayName: this._getDashboard(dashboardId).displayName,\n status: this._getDashboard(dashboardId).status,\n })),\n }));\n }\n\n /**\n * @private\n * @returns {Promise<{id: number, name: string, dashboard_ids: number[]}[]>}\n */\n _fetchGroups() {\n return this.orm.searchRead(\n \"spreadsheet.dashboard.group\",\n [[\"dashboard_ids\", \"!=\", false]],\n [\"id\", \"name\", \"dashboard_ids\"]\n );\n }\n\n /**\n * @private\n * @param {Array} groups\n * @returns {Promise}\n */\n _fetchDashboardNames(groups) {\n return this.orm.read(\n \"spreadsheet.dashboard\",\n groups.map((group) => group.dashboardIds).flat(),\n [\"name\"]\n );\n }\n\n /**\n * @private\n * @param {number} id\n * @returns {Dashboard|undefined}\n */\n _getDashboard(id) {\n if (!this.dashboards[id]) {\n throw new Error(`Dashboard ${id} does not exist`);\n }\n return this.dashboards[id];\n }\n\n /**\n * @private\n * @param {number} dashboardId\n */\n async _loadDashboardData(dashboardId) {\n const dashboard = this._getDashboard(dashboardId);\n dashboard.status = Status.Loading;\n try {\n const { data, revisions } = await this.fetchDashboardData(dashboardId);\n dashboard.model = this._createSpreadsheetModel(data, revisions);\n dashboard.status = Status.Loaded;\n } catch (error) {\n dashboard.error = error;\n dashboard.status = Status.Error;\n }\n }\n\n /**\n * Activate the first sheet of a model\n *\n * @param {Model} model\n */\n _activateFirstSheet(model) {\n const sheetId = model.getters.getActiveSheetId();\n const firstSheetId = model.getters.getSheetIds()[0];\n if (firstSheetId !== sheetId) {\n model.dispatch(\"ACTIVATE_SHEET\", {\n sheetIdFrom: sheetId,\n sheetIdTo: firstSheetId,\n });\n }\n }\n\n /**\n * @private\n * @param {string} data\n * @param {object[]} revisions\n * @returns {Model}\n */\n _createSpreadsheetModel(data, revisions = []) {\n const dataSources = new DataSources(this.orm);\n const model = new Model(\n migrate(data),\n {\n custom: { env: this.env, orm: this.orm, dataSources },\n mode: \"dashboard\",\n },\n revisions\n );\n this._activateFirstSheet(model);\n dataSources.addEventListener(\"data-source-updated\", () => model.dispatch(\"EVALUATE_CELLS\"));\n return model;\n }\n}\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nconst { Component, useSubEnv } = owl;\nconst { registries } = spreadsheet;\nconst { figureRegistry } = registries;\n\nexport class MobileFigureContainer extends Component {\n setup() {\n useSubEnv({\n model: this.props.spreadsheetModel,\n isDashboard: () => this.props.spreadsheetModel.getters.isDashboard(),\n });\n }\n\n get figures() {\n const sheetId = this.props.spreadsheetModel.getters.getActiveSheetId();\n return this.props.spreadsheetModel.getters\n .getFigures(sheetId)\n .sort((f1, f2) => (this.isBefore(f1, f2) ? -1 : 1))\n .map((figure) => ({\n ...figure,\n width: window.innerWidth,\n height: 300,\n }));\n }\n\n getFigureComponent(figure) {\n return figureRegistry.get(figure.tag).Component;\n }\n\n isBefore(f1, f2) {\n // TODO be smarter\n return f1.x < f2.x ? f1.y < f2.y : f1.y < f2.y;\n }\n}\n\nMobileFigureContainer.template = \"documents_spreadsheet.MobileFigureContainer\";\n\nMobileFigureContainer.props = {\n spreadsheetModel: Object,\n};\n", "/** @odoo-module */\n\nimport { _t } from \"@web/core/l10n/translation\";\n\nconst { Component, useState } = owl;\n\nexport class DashboardMobileSearchPanel extends Component {\n setup() {\n this.state = useState({ isOpen: false });\n }\n\n get searchBarText() {\n return this.props.activeDashboard\n ? this.props.activeDashboard.displayName\n : _t(\"Choose a dashboard....\");\n }\n\n onDashboardSelected(dashboardId) {\n this.props.onDashboardSelected(dashboardId);\n this.state.isOpen = false;\n }\n\n openDashboardSelection() {\n const dashboards = this.props.groups.map((group) => group.dashboards).flat();\n if (dashboards.length > 1) {\n this.state.isOpen = true;\n }\n }\n}\n\nDashboardMobileSearchPanel.template = \"documents_spreadsheet.DashboardMobileSearchPanel\";\nDashboardMobileSearchPanel.props = {\n /**\n * (dashboardId: number) => void\n */\n onDashboardSelected: Function,\n groups: Object,\n activeDashboard: {\n type: Object,\n optional: true,\n },\n};\n", "/** @odoo-module */\n\nimport { SEE_RECORD_LIST, SEE_RECORD_LIST_VISIBLE } from \"@spreadsheet/list/list_actions\";\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\n\nconst { clickableCellRegistry } = spreadsheet.registries;\n\nclickableCellRegistry.add(\"list\", {\n condition: SEE_RECORD_LIST_VISIBLE,\n action: SEE_RECORD_LIST,\n sequence: 10,\n});\n", "/** @odoo-module */\n\nimport spreadsheet from \"@spreadsheet/o_spreadsheet/o_spreadsheet_extended\";\nimport { SEE_RECORDS_PIVOT, SEE_RECORDS_PIVOT_VISIBLE } from \"@spreadsheet/pivot/pivot_actions\";\nimport { getFirstPivotFunction } from \"@spreadsheet/pivot/pivot_helpers\";\n\nconst { clickableCellRegistry } = spreadsheet.registries;\n\nclickableCellRegistry.add(\"pivot\", {\n condition: SEE_RECORDS_PIVOT_VISIBLE,\n action: SEE_RECORDS_PIVOT,\n sequence: 3,\n});\n\nclickableCellRegistry.add(\"pivot_set_filter_matching\", {\n condition: (position, env) => {\n const cell = env.model.getters.getCell(position);\n return (\n SEE_RECORDS_PIVOT_VISIBLE(position, env) &&\n getFirstPivotFunction(cell.content).functionName === \"ODOO.PIVOT.HEADER\" &&\n env.model.getters.getFiltersMatchingPivot(cell.content).length > 0\n );\n },\n action: (position, env) => {\n const cell = env.model.getters.getCell(position);\n const filters = env.model.getters.getFiltersMatchingPivot(cell.content);\n env.model.dispatch(\"SET_MANY_GLOBAL_FILTER_VALUE\", { filters });\n },\n sequence: 2,\n});\n", "/** @odoo-module */\n\nimport { SpreadsheetDashboardAction } from \"@spreadsheet_dashboard/bundle/dashboard_action/dashboard_action\";\nimport { patch } from \"@web/core/utils/patch\";\n\npatch(\n SpreadsheetDashboardAction.prototype,\n \"spreadsheet_dashboard_edition.SpreadsheetDashboardAction\",\n {\n /**\n * @param {number} dashboardId\n * @returns {Promise<{ data: string, revisions: object[] }>}\n */\n async _fetchDashboardData(dashboardId) {\n const data = await this.orm.call(\"spreadsheet.dashboard\", \"join_spreadsheet_session\", [\n dashboardId,\n ]);\n return { data: data.raw, revisions: data.revisions };\n },\n }\n);\n", "/** @odoo-module */\n\nimport { AbstractSpreadsheetAction } from \"@spreadsheet_edition/bundle/actions/abstract_spreadsheet_action\";\nimport { registry } from \"@web/core/registry\";\nimport SpreadsheetComponent from \"@spreadsheet_edition/bundle/actions/spreadsheet_component\";\nimport { SpreadsheetControlPanel } from \"@spreadsheet_edition/bundle/actions/control_panel/spreadsheet_control_panel\";\nimport { useService } from \"@web/core/utils/hooks\";\nimport { RecordFileStore } from \"@spreadsheet_edition/bundle/image/record_file_store\";\n\n/**\n * @typedef {import(\"@spreadsheet_edition/bundle/actions/abstract_spreadsheet_action\").SpreadsheetRecord} SpreadsheetRecord\n *\n * @typedef State\n * @property {number} numberOfConnectedUsers\n * @property {boolean} isSynced\n *\n * @typedef {import(\"@spreadsheet_edition/bundle/o_spreadsheet/collaborative/spreadsheet_collaborative_service\").SpreadsheetCollaborativeService} SpreadsheetCollaborativeService\n */\n\nconst { useState, Component, useSubEnv } = owl;\n\nclass DashboardEditAction extends AbstractSpreadsheetAction {\n setup() {\n super.setup();\n /** @type {State} */\n this.collaborativeState = useState({\n numberOfConnectedUsers: 1,\n isSynced: true,\n });\n useSubEnv({\n // TODO clean this env key\n isDashboardSpreadsheet: true,\n });\n\n /** @type {SpreadsheetCollaborativeService} */\n this.spreadsheetCollaborative = useService(\"spreadsheet_collaborative\");\n this.fileStore = new RecordFileStore(\n \"spreadsheet.dashboard\",\n this.resId,\n this.http,\n this.orm\n );\n }\n\n async onWillStart() {\n await super.onWillStart();\n this.transportService = this.spreadsheetCollaborative.getCollaborativeChannel(\n Component.env,\n \"spreadsheet.dashboard\",\n this.resId\n );\n }\n\n /**\n * @override\n * @returns {Promise}\n */\n async _fetchData() {\n const record = await this.orm.call(\"spreadsheet.dashboard\", \"join_spreadsheet_session\", [\n this.resId,\n ]);\n return record;\n }\n\n /**\n * @override\n * @param {SpreadsheetRecord} record\n */\n _initializeWith(record) {\n this.spreadsheetData = record.raw;\n this.stateUpdateMessages = record.revisions;\n this.snapshotRequested = record.snapshot_requested;\n this.state.spreadsheetName = record.name;\n this.isReadonly = record.isReadonly;\n }\n\n /**\n * Updates the control panel with the sync status of spreadsheet\n *\n * @param {{ synced: boolean, numberOfConnectedUsers: number }}\n */\n _onSpreadsheetSyncStatus({ synced, numberOfConnectedUsers }) {\n this.collaborativeState.isSynced = synced;\n this.collaborativeState.numberOfConnectedUsers = numberOfConnectedUsers;\n }\n\n async _onSpreadSheetNameChanged(detail) {\n const { name } = detail;\n this.state.spreadsheetName = name;\n this.env.config.setDisplayName(this.state.spreadsheetName);\n await this.orm.write(\"spreadsheet.dashboard\", [this.resId], {\n name,\n });\n }\n\n async _onDownload() {\n //TODO\n }\n\n /**\n * Reload the spreadsheet if an unexpected revision id is triggered.\n */\n _onUnexpectedRevisionId() {\n this.actionService.doAction(\"reload_context\");\n }\n\n async _onSpreadsheetSaved({ thumbnail }) {\n await this.orm.write(\"spreadsheet.dashboard\", [this.resId], { thumbnail });\n }\n}\n\nDashboardEditAction.template = \"spreadsheet_dashboard_edition.DashboardEditAction\";\nDashboardEditAction.components = {\n SpreadsheetControlPanel,\n SpreadsheetComponent,\n};\n\nregistry.category(\"actions\").add(\"action_edit_dashboard\", DashboardEditAction, { force: true });\n", "/** @odoo-module */\n\nimport { registry } from \"@web/core/registry\";\nimport { mockJoinSpreadsheetSession } from \"@spreadsheet_edition/../tests/utils/mock_server\";\n\nregistry\n .category(\"mock_server\")\n .add(\n \"spreadsheet.dashboard/join_spreadsheet_session\",\n mockJoinSpreadsheetSession(\"spreadsheet.dashboard\")\n );\n"], "file": "/web/assets/998-8352a9d/web.qunit_mobile_suite_tests.js", "sourceRoot": "../../../"}