chuijs 4.0.3 → 4.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +536 -0
- package/framework/appLayout/default/chui_app_layout.js +361 -303
- package/framework/appLayout/default/global_style.css +243 -89
- package/framework/appLayout/default/main_theme_style.css +188 -101
- package/framework/components/chui_accordion/accordion.js +17 -17
- package/framework/components/chui_accordion/styles.css +7 -7
- package/framework/components/chui_action_sheet/action_sheet.js +35 -32
- package/framework/components/chui_action_sheet/styles.css +10 -4
- package/framework/components/chui_actions_bar/actions_bar.js +338 -0
- package/framework/components/chui_actions_bar/styles.css +139 -0
- package/framework/components/chui_affix/affix.js +4 -4
- package/framework/components/chui_affix/styles.css +5 -0
- package/framework/components/chui_alert/alert.js +11 -5
- package/framework/components/chui_alert/styles.css +7 -0
- package/framework/components/chui_avatar/avatar.js +13 -13
- package/framework/components/chui_avatar/styles.css +1 -1
- package/framework/components/chui_avatar_group/avatar_group.js +4 -4
- package/framework/components/chui_avatar_group/styles.css +1 -1
- package/framework/components/chui_backtop/backtop.js +25 -2
- package/framework/components/chui_backtop/styles.css +21 -0
- package/framework/components/chui_badge/badge.js +15 -15
- package/framework/components/chui_badge/styles.css +6 -6
- package/framework/components/chui_banner/banner.js +222 -0
- package/framework/components/chui_banner/styles.css +170 -0
- package/framework/components/chui_breadcrumb/styles.css +1 -1
- package/framework/components/chui_button/button.js +24 -28
- package/framework/components/chui_button/styles.css +5 -5
- package/framework/components/chui_calendar/calendar.js +6 -6
- package/framework/components/chui_card/card.js +14 -14
- package/framework/components/chui_card_scroll/card_scroll.js +169 -0
- package/framework/components/chui_card_scroll/styles.css +86 -0
- package/framework/components/chui_chat/chat.js +73 -51
- package/framework/components/chui_chat/styles.css +18 -7
- package/framework/components/chui_clipboard_history/clipboard_history.js +25 -20
- package/framework/components/chui_clipboard_history/styles.css +10 -8
- package/framework/components/chui_code/code.js +8 -8
- package/framework/components/chui_code/styles.css +2 -2
- package/framework/components/chui_code_editor/code_editor.js +26 -26
- package/framework/components/chui_code_editor/styles.css +3 -3
- package/framework/components/chui_command_palette/command_palette.js +29 -29
- package/framework/components/chui_command_palette/styles.css +5 -5
- package/framework/components/chui_console/console.js +9 -9
- package/framework/components/chui_console/styles.css +5 -2
- package/framework/components/chui_content_block/content_block.js +2 -2
- package/framework/components/chui_context_menu/context_menu.js +162 -54
- package/framework/components/chui_context_menu/styles.css +82 -8
- package/framework/components/chui_custom_element/custom_element.js +11 -11
- package/framework/components/chui_data_grid/data_grid.js +268 -268
- package/framework/components/chui_data_grid/styles.css +14 -14
- package/framework/components/chui_descriptions/styles.css +1 -1
- package/framework/components/chui_details/details.js +12 -12
- package/framework/components/chui_details/styles.css +7 -7
- package/framework/components/chui_diff_viewer/diff_viewer.js +88 -88
- package/framework/components/chui_diff_viewer/styles.css +9 -9
- package/framework/components/chui_divider/divider.js +6 -6
- package/framework/components/chui_divider/styles.css +2 -2
- package/framework/components/chui_drawer/drawer.js +37 -14
- package/framework/components/chui_drawer/styles.css +18 -4
- package/framework/components/chui_drop_zone/drop_zone.js +6 -6
- package/framework/components/chui_drop_zone/styles.css +10 -6
- package/framework/components/chui_fieldset/fieldset.js +6 -6
- package/framework/components/chui_fieldset/styles.css +5 -5
- package/framework/components/chui_file_explorer/file_explorer.js +115 -115
- package/framework/components/chui_file_explorer/styles.css +6 -6
- package/framework/components/chui_form/form.js +11 -11
- package/framework/components/chui_gantt/gantt.js +41 -41
- package/framework/components/chui_gantt/styles.css +5 -5
- package/framework/components/chui_graphs/graphs.js +82 -46
- package/framework/components/chui_graphs/styles.css +26 -5
- package/framework/components/chui_hero/hero.js +63 -20
- package/framework/components/chui_hero/styles.css +176 -7
- package/framework/components/chui_html_block/html_block.js +5 -5
- package/framework/components/chui_html_block/styles.css +1 -1
- package/framework/components/chui_hx/styles.css +2 -2
- package/framework/components/chui_icons/icons.js +7 -7
- package/framework/components/chui_icons/styles.css +2 -1
- package/framework/components/chui_inputs/chui_check_box/styles.css +12 -3
- package/framework/components/chui_inputs/chui_chips/chips.js +92 -31
- package/framework/components/chui_inputs/chui_chips/styles.css +28 -5
- package/framework/components/chui_inputs/chui_color_picker/color_picker.js +20 -15
- package/framework/components/chui_inputs/chui_color_picker/styles.css +7 -7
- package/framework/components/chui_inputs/chui_combo_box/combo_box.js +58 -58
- package/framework/components/chui_inputs/chui_combo_box/styles.css +9 -9
- package/framework/components/chui_inputs/chui_date/date.js +93 -95
- package/framework/components/chui_inputs/chui_date/styles.css +8 -8
- package/framework/components/chui_inputs/chui_date_range/date_range.js +22 -22
- package/framework/components/chui_inputs/chui_date_range/styles.css +3 -3
- package/framework/components/chui_inputs/chui_datetime/datetime.js +12 -12
- package/framework/components/chui_inputs/chui_datetime/styles.css +3 -3
- package/framework/components/chui_inputs/chui_email/email.js +10 -10
- package/framework/components/chui_inputs/chui_email/styles.css +6 -6
- package/framework/components/chui_inputs/chui_file/file.js +8 -8
- package/framework/components/chui_inputs/chui_file/styles.css +12 -9
- package/framework/components/chui_inputs/chui_hotkey/hotkey.js +39 -39
- package/framework/components/chui_inputs/chui_hotkey/styles.css +6 -6
- package/framework/components/chui_inputs/chui_masked/masked.js +44 -44
- package/framework/components/chui_inputs/chui_masked/styles.css +5 -5
- package/framework/components/chui_inputs/chui_mention/mention.js +13 -13
- package/framework/components/chui_inputs/chui_mention/styles.css +14 -8
- package/framework/components/chui_inputs/chui_multi_combo_box/multi_combo_box.js +147 -137
- package/framework/components/chui_inputs/chui_multi_combo_box/styles.css +14 -8
- package/framework/components/chui_inputs/chui_number/number.js +42 -45
- package/framework/components/chui_inputs/chui_number/styles.css +6 -6
- package/framework/components/chui_inputs/chui_otp/otp.js +32 -32
- package/framework/components/chui_inputs/chui_otp/styles.css +5 -5
- package/framework/components/chui_inputs/chui_password/password.js +30 -36
- package/framework/components/chui_inputs/chui_password/styles.css +7 -7
- package/framework/components/chui_inputs/chui_radio_button/styles.css +2 -2
- package/framework/components/chui_inputs/chui_rating/rating.js +28 -28
- package/framework/components/chui_inputs/chui_rating/styles.css +3 -3
- package/framework/components/chui_inputs/chui_select_box/select_box.js +54 -54
- package/framework/components/chui_inputs/chui_select_box/styles.css +10 -10
- package/framework/components/chui_inputs/chui_slider/slider.js +14 -14
- package/framework/components/chui_inputs/chui_slider/styles.css +5 -4
- package/framework/components/chui_inputs/chui_text/styles.css +7 -7
- package/framework/components/chui_inputs/chui_text/text.js +13 -17
- package/framework/components/chui_inputs/chui_text_area/styles.css +8 -8
- package/framework/components/chui_inputs/chui_text_area/text_area.js +23 -23
- package/framework/components/chui_inputs/chui_time/styles.css +13 -17
- package/framework/components/chui_inputs/chui_time/time.js +35 -24
- package/framework/components/chui_inputs/chui_toggle/styles.css +10 -3
- package/framework/components/chui_inputs/chui_toggle/toggle.js +16 -16
- package/framework/components/chui_json_editor/json_editor.js +87 -87
- package/framework/components/chui_json_editor/styles.css +5 -5
- package/framework/components/chui_json_viewer/json_viewer.js +80 -80
- package/framework/components/chui_json_viewer/styles.css +6 -6
- package/framework/components/chui_kanban/kanban.js +27 -27
- package/framework/components/chui_kanban/styles.css +8 -4
- package/framework/components/chui_kbd/styles.css +1 -1
- package/framework/components/chui_label/label.js +17 -17
- package/framework/components/chui_label/styles.css +1 -1
- package/framework/components/chui_link/link.js +9 -9
- package/framework/components/chui_link/styles.css +11 -4
- package/framework/components/chui_list/list.js +11 -11
- package/framework/components/chui_list/styles.css +2 -2
- package/framework/components/chui_markdown_editor/markdown_editor.js +66 -66
- package/framework/components/chui_markdown_editor/styles.css +5 -5
- package/framework/components/chui_media/audio.js +240 -246
- package/framework/components/chui_media/audio_styles.css +41 -23
- package/framework/components/chui_media/image.js +49 -46
- package/framework/components/chui_media/image_compare.js +35 -35
- package/framework/components/chui_media/image_compare_styles.css +5 -5
- package/framework/components/chui_media/image_styles.css +1 -1
- package/framework/components/chui_media/video.js +148 -148
- package/framework/components/chui_media/video_styles.css +27 -11
- package/framework/components/chui_menu_bar/menu_bar.js +54 -54
- package/framework/components/chui_menu_bar/styles.css +10 -10
- package/framework/components/chui_message_bar/message_bar.js +551 -0
- package/framework/components/chui_message_bar/styles.css +248 -0
- package/framework/components/chui_modal/modal.js +37 -13
- package/framework/components/chui_notification/notification.js +76 -54
- package/framework/components/chui_notification/notification_download.js +26 -81
- package/framework/components/chui_notification/notification_download_card.js +115 -0
- package/framework/components/chui_notification/notification_download_progress.css +2 -2
- package/framework/components/chui_notification/notification_download_progress.js +70 -108
- package/framework/components/chui_notification/notification_update.js +31 -31
- package/framework/components/chui_notification/status_plate.css +3 -3
- package/framework/components/chui_notification/styles.css +6 -1
- package/framework/components/chui_notification/styles_download.css +1 -1
- package/framework/components/chui_notification/styles_update.css +2 -2
- package/framework/components/chui_org_chart/org_chart.js +29 -29
- package/framework/components/chui_org_chart/styles.css +2 -2
- package/framework/components/chui_pagination/pagination.js +3 -3
- package/framework/components/chui_pagination/styles.css +1 -1
- package/framework/components/chui_paragraph/styles.css +2 -2
- package/framework/components/chui_popover/popover.js +29 -23
- package/framework/components/chui_popover/styles.css +1 -1
- package/framework/components/chui_popups/popups.js +124 -124
- package/framework/components/chui_popups/styles.css +5 -5
- package/framework/components/chui_print_button/print_button.js +15 -10
- package/framework/components/chui_progress_bar/progress_bar.js +34 -12
- package/framework/components/chui_progress_bar/styles.css +4 -4
- package/framework/components/chui_progress_ring/progress_ring.js +15 -15
- package/framework/components/chui_progress_ring/styles.css +1 -1
- package/framework/components/chui_qrcode/qrcode.js +12 -12
- package/framework/components/chui_query_builder/query_builder.js +46 -46
- package/framework/components/chui_query_builder/styles.css +4 -4
- package/framework/components/chui_radio_group/styles.css +1 -1
- package/framework/components/chui_result/result.js +15 -15
- package/framework/components/chui_result/styles.css +3 -3
- package/framework/components/chui_scheduler/scheduler.js +74 -74
- package/framework/components/chui_scheduler/styles.css +5 -5
- package/framework/components/chui_screen_spinner/screen_spinner.js +167 -0
- package/framework/components/chui_screen_spinner/styles.css +49 -0
- package/framework/components/chui_segmented_control/segmented_control.js +63 -6
- package/framework/components/chui_segmented_control/styles.css +32 -6
- package/framework/components/chui_settings/settings.js +45 -46
- package/framework/components/chui_settings/styles.css +4 -4
- package/framework/components/chui_shortcut_help/shortcut_help.js +32 -34
- package/framework/components/chui_sidebar_nav/sidebar_nav.js +98 -29
- package/framework/components/chui_sidebar_nav/styles.css +9 -3
- package/framework/components/chui_slideshow/slideshow.js +85 -85
- package/framework/components/chui_slideshow/styles.css +6 -6
- package/framework/components/chui_spinner/spinner.js +10 -10
- package/framework/components/chui_split_button/split_button.js +30 -12
- package/framework/components/chui_split_button/styles.css +21 -5
- package/framework/components/chui_splitter/splitter.js +11 -11
- package/framework/components/chui_splitter/styles.css +2 -2
- package/framework/components/chui_statistic/statistic.js +47 -47
- package/framework/components/chui_status_bar/styles.css +2 -2
- package/framework/components/chui_stepper/stepper.js +202 -31
- package/framework/components/chui_stepper/styles.css +79 -14
- package/framework/components/chui_sticky_bar/sticky_bar.js +44 -13
- package/framework/components/chui_sticky_bar/styles.css +10 -8
- package/framework/components/chui_tabs/chui_tabs.js +74 -26
- package/framework/components/chui_tabs/styles.css +40 -12
- package/framework/components/chui_text_editor/styles.css +7 -7
- package/framework/components/chui_text_editor/text_editor.js +56 -56
- package/framework/components/chui_text_editor/text_editor_panel.js +173 -174
- package/framework/components/chui_theme_toggle/theme_toggle.js +21 -5
- package/framework/components/chui_timeline/styles.css +3 -1
- package/framework/components/chui_toast_queue/styles.css +11 -11
- package/framework/components/chui_toast_queue/toast_queue.js +30 -27
- package/framework/components/chui_toolbar/styles.css +2 -2
- package/framework/components/chui_tooltip/styles.css +2 -2
- package/framework/components/chui_tooltip/tooltip.js +3 -3
- package/framework/components/chui_tour/styles.css +8 -8
- package/framework/components/chui_tour/tour.js +72 -72
- package/framework/components/chui_transfer/styles.css +6 -6
- package/framework/components/chui_transfer/transfer.js +93 -106
- package/framework/components/chui_tree_table/styles.css +4 -4
- package/framework/components/chui_tree_table/tree_table.js +45 -45
- package/framework/components/chui_tree_view/styles.css +10 -8
- package/framework/components/chui_tree_view/tree_view.js +185 -183
- package/framework/components/chui_user_bar/styles.css +145 -0
- package/framework/components/chui_user_bar/user_bar.js +402 -0
- package/framework/components/chui_user_list_bar/styles.css +173 -0
- package/framework/components/chui_user_list_bar/user_list_bar.js +485 -0
- package/framework/components/chui_watermark/watermark.js +17 -17
- package/framework/components/chui_webview/webview.js +9 -9
- package/framework/components/chui_write_bar/styles.css +172 -0
- package/framework/components/chui_write_bar/write_bar.js +320 -0
- package/framework/main_window.js +1234 -0
- package/framework/modules/chui_a11y/chui_a11y.js +78 -79
- package/framework/modules/chui_animations/animations.js +77 -15
- package/framework/modules/chui_animations/motion.js +120 -0
- package/framework/modules/chui_animations/styles.css +36 -3
- package/framework/modules/chui_functions.js +31 -27
- package/framework/modules/chui_highlight/styles.css +13 -10
- package/framework/modules/chui_i18n/chui_i18n.js +3 -3
- package/framework/modules/chui_i18n/locales/feedback.js +62 -0
- package/framework/modules/chui_i18n/locales/navigation.js +4 -0
- package/framework/modules/chui_json/chui_json.js +2 -2
- package/framework/modules/chui_logger/chui_logger.js +23 -8
- package/framework/modules/chui_page/page.js +10 -10
- package/framework/modules/chui_palette/palette.js +61 -0
- package/framework/modules/chui_qr/chui_qr.js +23 -23
- package/framework/modules/chui_scrollbar/chui_scrollbar.js +45 -45
- package/framework/modules/chui_scrollbar/styles.css +3 -3
- package/framework/modules/chui_services/chui_services.js +8 -8
- package/framework/modules/chui_services/desktop.js +77 -71
- package/framework/modules/chui_sortable/chui_sortable.js +17 -17
- package/framework/modules/chui_validate/chui_validate.js +3 -3
- package/framework/tray_bar/chui_menu_items.js +2 -2
- package/index.d.ts +266 -34
- package/index.js +20 -1168
- package/package.json +6 -4
- package/framework/components/telegram_bot/chui_telegram_bot.js +0 -191
|
@@ -18,25 +18,25 @@ const store = new Store();
|
|
|
18
18
|
* (`setPin("top" | "bottom" | "left" | "right")`) — тогда она висит поверх содержимого.
|
|
19
19
|
*/
|
|
20
20
|
class Audio {
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
21
|
+
#chuiApMain = document.createElement("chui_ap_main");
|
|
22
|
+
#chuiApBlock = document.createElement("chui_ap_block");
|
|
23
|
+
#chuiApRow = document.createElement("chui_ap_row");
|
|
24
|
+
#chuiAt = document.createElement("audio");
|
|
25
25
|
// Управление
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
26
|
+
#chuiApPlayPause = document.createElement("chui_ap_play_pause");
|
|
27
|
+
#chuiApInfo = document.createElement("chui_ap_info");
|
|
28
|
+
#chuiApTime1 = undefined;
|
|
29
|
+
#chuiApTime2 = undefined;
|
|
30
|
+
#chuiApSeekBlock = document.createElement("chui_ap_seek_block");
|
|
31
|
+
#chuiApSeek = document.createElement("input");
|
|
32
|
+
#chuiApSeekBuf = document.createElement("chui_ap_seek_buf");
|
|
33
|
+
#chuiApVolumeBlock = document.createElement("chui_ap_volume_block");
|
|
34
|
+
#chuiApVolumeIcon = document.createElement("chui_ap_volume_icon");
|
|
35
|
+
#chuiApVolume = document.createElement("input");
|
|
36
36
|
// Эквалайзер
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
37
|
+
#chuiApFxIcon = document.createElement("chui_ap_fx_icon");
|
|
38
|
+
#chuiApFxDropdown = document.createElement("chui_ap_fx_dropdown");
|
|
39
|
+
#chuiAudioFx = new AudioFX(this.#chuiAt);
|
|
40
40
|
// Состояние
|
|
41
41
|
#status = document.createElement("div");
|
|
42
42
|
#pin = false;
|
|
@@ -65,99 +65,99 @@ class Audio {
|
|
|
65
65
|
}
|
|
66
66
|
) {
|
|
67
67
|
require("../../modules/chui_functions").setStyles(__dirname + "/audio_styles.css", "chUiJS_Audio");
|
|
68
|
-
this.#
|
|
69
|
-
this.#
|
|
68
|
+
this.#chuiAt.preload = "metadata";
|
|
69
|
+
this.#chuiAt.controls = false;
|
|
70
70
|
// Плеер — именованная группа: без имени скринридер читает подряд безымянные кнопки
|
|
71
|
-
this.#
|
|
72
|
-
this.#
|
|
71
|
+
this.#chuiApMain.setAttribute("role", "group");
|
|
72
|
+
this.#chuiApMain.setAttribute("aria-label", optionOr(options.label, I18n.t("media.audioPlayer")));
|
|
73
73
|
// НАСТРОЙКИ
|
|
74
|
-
if (options.src !== undefined) this.#
|
|
75
|
-
if (options.autoplay !== undefined) this.#
|
|
76
|
-
if (options.loop !== undefined) this.#
|
|
77
|
-
if (options.muted !== undefined) this.#
|
|
78
|
-
this.#
|
|
79
|
-
if (options.width !== undefined) this.#
|
|
80
|
-
if (options.height !== undefined) this.#
|
|
74
|
+
if (options.src !== undefined) this.#chuiAt.setAttribute("src", options.src);
|
|
75
|
+
if (options.autoplay !== undefined) this.#chuiAt.autoplay = options.autoplay === true;
|
|
76
|
+
if (options.loop !== undefined) this.#chuiAt.loop = options.loop === true;
|
|
77
|
+
if (options.muted !== undefined) this.#chuiAt.muted = options.muted === true;
|
|
78
|
+
this.#chuiAt.volume = options.volume === undefined ? 1 : Math.min(1, Math.max(0, Number(options.volume)));
|
|
79
|
+
if (options.width !== undefined) this.#chuiApMain.style.width = options.width;
|
|
80
|
+
if (options.height !== undefined) this.#chuiApMain.style.height = options.height;
|
|
81
81
|
// ПОЛОСА ПРОГРЕССА
|
|
82
|
-
this.#
|
|
83
|
-
this.#
|
|
84
|
-
this.#
|
|
85
|
-
this.#
|
|
86
|
-
this.#
|
|
87
|
-
this.#
|
|
88
|
-
this.#
|
|
89
|
-
this.#
|
|
82
|
+
this.#chuiApTime1 = new Label({ text: `0:00` });
|
|
83
|
+
this.#chuiApTime2 = new Label({ text: `0:00` });
|
|
84
|
+
this.#chuiApSeek.type = "range";
|
|
85
|
+
this.#chuiApSeek.id = "chui_ap_seek";
|
|
86
|
+
this.#chuiApSeek.max = "0";
|
|
87
|
+
this.#chuiApSeek.value = "0";
|
|
88
|
+
this.#chuiApSeek.step = "any";
|
|
89
|
+
this.#chuiApSeek.setAttribute("aria-label", I18n.t("media.position"));
|
|
90
90
|
// До метаданных длительность неизвестна: ползунок включается в #setSliderMax()
|
|
91
91
|
this.#setSliderMax();
|
|
92
|
-
this.#
|
|
93
|
-
this.#
|
|
94
|
-
this.#
|
|
95
|
-
this.#
|
|
96
|
-
this.#
|
|
97
|
-
//
|
|
98
|
-
this.#setButton(this.#
|
|
99
|
-
this.#setButton(this.#
|
|
100
|
-
this.#setButton(this.#
|
|
92
|
+
this.#chuiApSeekBlock.appendChild(this.#chuiApSeek);
|
|
93
|
+
this.#chuiApSeekBlock.appendChild(this.#chuiApSeekBuf);
|
|
94
|
+
this.#chuiApInfo.appendChild(this.#chuiApTime1.set());
|
|
95
|
+
this.#chuiApInfo.appendChild(this.#chuiApSeekBlock);
|
|
96
|
+
this.#chuiApInfo.appendChild(this.#chuiApTime2.set());
|
|
97
|
+
// КНОПКИ: кегль глифа задаёт стиль кнопки (--player_icon_size), значок его наследует
|
|
98
|
+
this.#setButton(this.#chuiApPlayPause, I18n.t("media.play"), Icons.AUDIO_VIDEO.PLAY_ARROW);
|
|
99
|
+
this.#setButton(this.#chuiApVolumeIcon, I18n.t("media.mute"), Icons.AUDIO_VIDEO.VOLUME_UP);
|
|
100
|
+
this.#setButton(this.#chuiApFxIcon, I18n.t("media.equalizer"), Icons.AUDIO_VIDEO.EQUALIZER);
|
|
101
101
|
// ГРОМКОСТЬ
|
|
102
|
-
this.#
|
|
103
|
-
this.#
|
|
104
|
-
this.#
|
|
105
|
-
this.#
|
|
106
|
-
this.#
|
|
107
|
-
this.#
|
|
108
|
-
this.#
|
|
109
|
-
this.#
|
|
110
|
-
this.#
|
|
102
|
+
this.#chuiApVolume.type = "range";
|
|
103
|
+
this.#chuiApVolume.id = "chui_ap_volume";
|
|
104
|
+
this.#chuiApVolume.max = "100";
|
|
105
|
+
this.#chuiApVolume.value = String(Math.round(this.#chuiAt.volume * 100));
|
|
106
|
+
this.#chuiApVolume.step = "1";
|
|
107
|
+
this.#chuiApVolume.setAttribute("aria-label", I18n.t("media.volume"));
|
|
108
|
+
this.#chuiApVolumeBlock.appendChild(this.#chuiApVolumeIcon);
|
|
109
|
+
this.#chuiApVolumeBlock.appendChild(this.#chuiApVolume);
|
|
110
|
+
this.#chuiApVolumeBlock.appendChild(this.#chuiApFxIcon);
|
|
111
111
|
// СБОРКА: одна строка — воспроизведение, прогресс со временем, звук и эквалайзер
|
|
112
|
-
this.#
|
|
113
|
-
this.#
|
|
114
|
-
this.#
|
|
115
|
-
this.#
|
|
112
|
+
this.#chuiApRow.appendChild(this.#chuiApPlayPause);
|
|
113
|
+
this.#chuiApRow.appendChild(this.#chuiApInfo);
|
|
114
|
+
this.#chuiApRow.appendChild(this.#chuiApVolumeBlock);
|
|
115
|
+
this.#chuiApBlock.appendChild(this.#chuiApRow);
|
|
116
116
|
// Эквалайзер — секция внутри карточки, под полосой управления: наложением он быть
|
|
117
117
|
// не может, у карточки `overflow: hidden` (её скругление)
|
|
118
|
-
this.#
|
|
119
|
-
this.#
|
|
120
|
-
this.#
|
|
121
|
-
this.#
|
|
118
|
+
this.#chuiApBlock.appendChild(this.#chuiApFxDropdown);
|
|
119
|
+
this.#chuiApFxDropdown.appendChild(this.#chuiAudioFx.set());
|
|
120
|
+
this.#chuiApMain.appendChild(this.#chuiAt);
|
|
121
|
+
this.#chuiApMain.appendChild(this.#chuiApBlock);
|
|
122
122
|
// О смене состояния и ошибках сообщает скрытый live-регион
|
|
123
123
|
this.#status.classList.add("chui_visually_hidden");
|
|
124
124
|
this.#status.setAttribute("role", "status");
|
|
125
125
|
this.#status.setAttribute("aria-live", "polite");
|
|
126
|
-
this.#
|
|
126
|
+
this.#chuiApMain.appendChild(this.#status);
|
|
127
127
|
// СОБЫТИЯ
|
|
128
128
|
// Состояние кнопки идёт от самого элемента: play() может отклониться политикой
|
|
129
129
|
// автозапуска, и «пауза» на кнопке была бы неправдой
|
|
130
|
-
this.#
|
|
131
|
-
this.#
|
|
132
|
-
this.#
|
|
133
|
-
this.#
|
|
134
|
-
this.#
|
|
135
|
-
this.#
|
|
136
|
-
this.#
|
|
137
|
-
this.#
|
|
138
|
-
this.#
|
|
139
|
-
this.#
|
|
140
|
-
this.#
|
|
141
|
-
this.#
|
|
130
|
+
this.#chuiAt.addEventListener("play", this.#onPlay);
|
|
131
|
+
this.#chuiAt.addEventListener("pause", this.#onPause);
|
|
132
|
+
this.#chuiAt.addEventListener("timeupdate", this.#onTimeUpdate);
|
|
133
|
+
this.#chuiAt.addEventListener("loadedmetadata", this.#onDurationChange);
|
|
134
|
+
this.#chuiAt.addEventListener("durationchange", this.#onDurationChange);
|
|
135
|
+
this.#chuiAt.addEventListener("progress", () => this.#displayBufferedAmount());
|
|
136
|
+
this.#chuiAt.addEventListener("volumechange", () => this.#renderVolume());
|
|
137
|
+
this.#chuiAt.addEventListener("error", () => this.#onMediaError());
|
|
138
|
+
this.#chuiApPlayPause.addEventListener("click", () => this.#playPause());
|
|
139
|
+
this.#chuiApVolumeIcon.addEventListener("click", () => this.setMuted(!this.getMuted()));
|
|
140
|
+
this.#chuiApSeek.addEventListener("input", () => {
|
|
141
|
+
this.#chuiAt.currentTime = Number(Number(this.#chuiApSeek.value).toFixed(6));
|
|
142
142
|
this.#renderProgress();
|
|
143
143
|
});
|
|
144
|
-
this.#
|
|
145
|
-
this.#
|
|
146
|
-
this.#
|
|
144
|
+
this.#chuiApVolume.addEventListener("input", () => {
|
|
145
|
+
this.#chuiAt.volume = Number(this.#chuiApVolume.value) / 100;
|
|
146
|
+
this.#chuiAt.muted = false;
|
|
147
147
|
});
|
|
148
148
|
// ЭКВАЛАЙЗЕР: панель открывает кнопка, закрывают повторное нажатие, клик мимо и Escape
|
|
149
|
-
this.#
|
|
150
|
-
this.#
|
|
151
|
-
this.#
|
|
149
|
+
this.#chuiApFxIcon.setAttribute("aria-haspopup", "true");
|
|
150
|
+
this.#chuiApFxIcon.setAttribute("aria-expanded", "false");
|
|
151
|
+
this.#chuiApFxIcon.addEventListener("click", (event) => {
|
|
152
152
|
// Клик по кнопке не должен тут же закрывать панель общим обработчиком окна
|
|
153
153
|
event.stopPropagation();
|
|
154
154
|
this.#toggleFx();
|
|
155
155
|
});
|
|
156
|
-
this.#
|
|
156
|
+
this.#chuiApFxDropdown.addEventListener("keydown", (event) => {
|
|
157
157
|
if (event.key !== "Escape") return;
|
|
158
158
|
event.preventDefault();
|
|
159
159
|
this.#toggleFx(false);
|
|
160
|
-
this.#
|
|
160
|
+
this.#chuiApFxIcon.focus();
|
|
161
161
|
});
|
|
162
162
|
this.#setMediaActions();
|
|
163
163
|
this.#renderPlayState(false);
|
|
@@ -165,17 +165,17 @@ class Audio {
|
|
|
165
165
|
this.setPin(options.pin === undefined ? false : options.pin);
|
|
166
166
|
}
|
|
167
167
|
#toggleFx(open = undefined) {
|
|
168
|
-
const следующий = open === undefined ? !this.#
|
|
169
|
-
if (следующий === this.#
|
|
170
|
-
this.#
|
|
171
|
-
this.#
|
|
172
|
-
this.#
|
|
168
|
+
const следующий = open === undefined ? !this.#chuiApFxDropdown.classList.contains("fx_open") : open;
|
|
169
|
+
if (следующий === this.#chuiApFxDropdown.classList.contains("fx_open")) return;
|
|
170
|
+
this.#chuiApFxDropdown.classList.toggle("fx_open", следующий);
|
|
171
|
+
this.#chuiApFxIcon.classList.toggle("fx_icon_open", следующий);
|
|
172
|
+
this.#chuiApFxIcon.setAttribute("aria-expanded", String(следующий));
|
|
173
173
|
if (следующий) document.addEventListener("mousedown", this.#onFxOutside);
|
|
174
174
|
else document.removeEventListener("mousedown", this.#onFxOutside);
|
|
175
175
|
}
|
|
176
176
|
#onFxOutside = (event) => {
|
|
177
|
-
if (this.#
|
|
178
|
-
if (this.#
|
|
177
|
+
if (this.#chuiApFxDropdown.contains(event.target)) return;
|
|
178
|
+
if (this.#chuiApFxIcon.contains(event.target)) return;
|
|
179
179
|
this.#toggleFx(false);
|
|
180
180
|
};
|
|
181
181
|
#playPause = async () => {
|
|
@@ -188,31 +188,26 @@ class Audio {
|
|
|
188
188
|
} else this.pause();
|
|
189
189
|
};
|
|
190
190
|
#renderPlayState(playing) {
|
|
191
|
-
this.#setIcon(
|
|
192
|
-
|
|
193
|
-
playing ? Icons.AUDIO_VIDEO.PAUSE : Icons.AUDIO_VIDEO.PLAY_ARROW,
|
|
194
|
-
"18px"
|
|
195
|
-
);
|
|
196
|
-
this.#setLabel(this.#chui_ap_play_pause, playing ? I18n.t("media.pause") : I18n.t("media.play"));
|
|
191
|
+
this.#setIcon(this.#chuiApPlayPause, playing ? Icons.AUDIO_VIDEO.PAUSE : Icons.AUDIO_VIDEO.PLAY_ARROW);
|
|
192
|
+
this.#setLabel(this.#chuiApPlayPause, playing ? I18n.t("media.pause") : I18n.t("media.play"));
|
|
197
193
|
}
|
|
198
194
|
#renderVolume = () => {
|
|
199
|
-
const доля = Math.min(100, Math.max(0, Math.round(this.#
|
|
200
|
-
this.#
|
|
201
|
-
this.#
|
|
202
|
-
this.#
|
|
195
|
+
const доля = Math.min(100, Math.max(0, Math.round(this.#chuiAt.volume * 100)));
|
|
196
|
+
this.#chuiApVolume.value = String(доля);
|
|
197
|
+
this.#chuiApVolume.disabled = this.getMuted();
|
|
198
|
+
this.#chuiApMain.style.setProperty("--volume_before_width", `${доля}%`);
|
|
203
199
|
this.#setIcon(
|
|
204
|
-
this.#
|
|
205
|
-
this.getMuted() || доля === 0 ? Icons.AUDIO_VIDEO.VOLUME_OFF : Icons.AUDIO_VIDEO.VOLUME_UP
|
|
206
|
-
"18px"
|
|
200
|
+
this.#chuiApVolumeIcon,
|
|
201
|
+
this.getMuted() || доля === 0 ? Icons.AUDIO_VIDEO.VOLUME_OFF : Icons.AUDIO_VIDEO.VOLUME_UP
|
|
207
202
|
);
|
|
208
|
-
this.#setLabel(this.#
|
|
203
|
+
this.#setLabel(this.#chuiApVolumeIcon, I18n.t(this.getMuted() ? "media.unmute" : "media.mute"));
|
|
209
204
|
};
|
|
210
205
|
#displayBufferedAmount = () => {
|
|
211
|
-
const max = Number(this.#
|
|
206
|
+
const max = Number(this.#chuiApSeek.max);
|
|
212
207
|
if (!(max > 0)) return;
|
|
213
208
|
try {
|
|
214
|
-
const конец = Math.floor((this.#
|
|
215
|
-
this.#
|
|
209
|
+
const конец = Math.floor((this.#chuiAt.buffered.end(0) / max) * 100);
|
|
210
|
+
this.#chuiApSeekBuf.style.width = `${Math.min(100, Math.max(0, конец))}%`;
|
|
216
211
|
} catch (_error) {
|
|
217
212
|
// Буфер ещё пуст (метаданные не пришли) — рисовать нечего
|
|
218
213
|
}
|
|
@@ -222,28 +217,28 @@ class Audio {
|
|
|
222
217
|
#setSliderMax = () => {
|
|
223
218
|
const duration = this.getDuration();
|
|
224
219
|
const известна = Number.isFinite(duration) && duration > 0;
|
|
225
|
-
this.#
|
|
220
|
+
this.#chuiApSeek.max = известна ? String(Math.floor(duration)) : "0";
|
|
226
221
|
// Без известной длительности ползунок бессмысленен: перетаскивание отправляло бы плеер в начало
|
|
227
|
-
this.#
|
|
222
|
+
this.#chuiApSeek.disabled = !известна;
|
|
228
223
|
};
|
|
229
224
|
#renderProgress = () => {
|
|
230
|
-
const max = Number(this.#
|
|
225
|
+
const max = Number(this.#chuiApSeek.max);
|
|
231
226
|
const position = this.getCurrentTime();
|
|
232
227
|
const duration = this.getDuration();
|
|
233
228
|
if (!(max > 0)) {
|
|
234
229
|
// «0:07 / 0:00» читалось бы как поломка: у источника без длительности только прошедшее
|
|
235
|
-
this.#
|
|
236
|
-
this.#
|
|
237
|
-
this.#
|
|
230
|
+
this.#chuiApTime1.setText(this.#calculateTime(position));
|
|
231
|
+
this.#chuiApTime2.setText("");
|
|
232
|
+
this.#chuiApMain.style.setProperty("--seek_before_width", "0%");
|
|
238
233
|
return;
|
|
239
234
|
}
|
|
240
|
-
this.#
|
|
241
|
-
this.#
|
|
242
|
-
this.#
|
|
235
|
+
this.#chuiApTime1.setText(this.#calculateTime(position));
|
|
236
|
+
this.#chuiApTime2.setText(this.#calculateTime(duration));
|
|
237
|
+
this.#chuiApSeek.value = String(position);
|
|
243
238
|
// Доля ограничена сотней: `max` — округлённая длительность, а позиция идёт дальше,
|
|
244
239
|
// и без ограничения полоса целиком заливалась акцентом
|
|
245
240
|
const доля = Math.min(100, Math.max(0, Math.floor((position / max) * 100)));
|
|
246
|
-
this.#
|
|
241
|
+
this.#chuiApMain.style.setProperty("--seek_before_width", `${доля}%`);
|
|
247
242
|
};
|
|
248
243
|
#calculateTime = (secs) => {
|
|
249
244
|
const seconds = Number.isFinite(Number(secs)) ? Number(secs) : 0;
|
|
@@ -252,7 +247,7 @@ class Audio {
|
|
|
252
247
|
return `${minutes}:${rest < 10 ? `0${rest}` : `${rest}`}`;
|
|
253
248
|
};
|
|
254
249
|
#onMediaError = () => {
|
|
255
|
-
const код = this.#
|
|
250
|
+
const код = this.#chuiAt.error === null ? undefined : this.#chuiAt.error.code;
|
|
256
251
|
const причина = Audio.#ERRORS[код] === undefined ? "" : `: ${I18n.t(Audio.#ERRORS[код])}`;
|
|
257
252
|
Log.error(`Audio: ошибка воспроизведения${причина}`);
|
|
258
253
|
this.#setStatus(`${I18n.t("media.audioNotPlaying", { reason: I18n.t("media.error.interrupted") })}${причина}`);
|
|
@@ -260,13 +255,13 @@ class Audio {
|
|
|
260
255
|
#setStatus(text) {
|
|
261
256
|
this.#status.innerText = text;
|
|
262
257
|
}
|
|
263
|
-
#setButton(node, label, icon
|
|
264
|
-
node.innerHTML = new Icon(icon
|
|
258
|
+
#setButton(node, label, icon) {
|
|
259
|
+
node.innerHTML = new Icon(icon).getHTML();
|
|
265
260
|
node.setAttribute("aria-label", label);
|
|
266
261
|
makeKeyboardButton(node);
|
|
267
262
|
}
|
|
268
|
-
#setIcon(node, icon
|
|
269
|
-
const глиф = new Icon(icon
|
|
263
|
+
#setIcon(node, icon) {
|
|
264
|
+
const глиф = new Icon(icon).getHTML();
|
|
270
265
|
if (node.innerHTML !== глиф) node.innerHTML = глиф;
|
|
271
266
|
}
|
|
272
267
|
#setLabel(node, label) {
|
|
@@ -305,8 +300,8 @@ class Audio {
|
|
|
305
300
|
return this;
|
|
306
301
|
}
|
|
307
302
|
this.#pin = значение;
|
|
308
|
-
if (значение === false) this.#
|
|
309
|
-
else this.#
|
|
303
|
+
if (значение === false) this.#chuiApMain.removeAttribute("data_pin");
|
|
304
|
+
else this.#chuiApMain.setAttribute("data_pin", значение);
|
|
310
305
|
return this;
|
|
311
306
|
}
|
|
312
307
|
getPin() {
|
|
@@ -315,10 +310,10 @@ class Audio {
|
|
|
315
310
|
// === ЭКВАЛАЙЗЕР ===
|
|
316
311
|
/** Включить или выключить эквалайзер: то же, что делает выключатель в панели */
|
|
317
312
|
setEqualizerEnabled(boolean = Boolean()) {
|
|
318
|
-
return (this.#
|
|
313
|
+
return (this.#chuiAudioFx.setEnabled(boolean), this);
|
|
319
314
|
}
|
|
320
315
|
isEqualizerEnabled() {
|
|
321
|
-
return this.#
|
|
316
|
+
return this.#chuiAudioFx.isEnabled();
|
|
322
317
|
}
|
|
323
318
|
/** Раскрыть или свернуть панель эквалайзера */
|
|
324
319
|
setEqualizerOpen(open = Boolean()) {
|
|
@@ -327,142 +322,142 @@ class Audio {
|
|
|
327
322
|
}
|
|
328
323
|
/** Показать панель эквалайзера, если она скрыта, и наоборот */
|
|
329
324
|
isEqualizerOpen() {
|
|
330
|
-
return this.#
|
|
325
|
+
return this.#chuiApFxDropdown.classList.contains("fx_open");
|
|
331
326
|
}
|
|
332
327
|
/** Прочитать сохранённое состояние эквалайзера из настроек */
|
|
333
328
|
restoreFX() {
|
|
334
|
-
this.#
|
|
329
|
+
this.#chuiAudioFx.restore();
|
|
335
330
|
return this;
|
|
336
331
|
}
|
|
337
332
|
// === СТАНДАРТНЫЕ ВОЗМОЖНОСТИ `<audio>` ===
|
|
338
333
|
/** Сам элемент: для всего, чего нет в списке ниже */
|
|
339
334
|
getAudio() {
|
|
340
|
-
return this.#
|
|
335
|
+
return this.#chuiAt;
|
|
341
336
|
}
|
|
342
337
|
/** Воспроизведение: промис, как у `<audio>` */
|
|
343
338
|
play() {
|
|
344
|
-
return this.#
|
|
339
|
+
return this.#chuiAt.play();
|
|
345
340
|
}
|
|
346
341
|
pause() {
|
|
347
|
-
this.#
|
|
342
|
+
this.#chuiAt.pause();
|
|
348
343
|
return this;
|
|
349
344
|
}
|
|
350
345
|
load() {
|
|
351
|
-
this.#
|
|
346
|
+
this.#chuiAt.load();
|
|
352
347
|
return this;
|
|
353
348
|
}
|
|
354
349
|
canPlayType(type = String()) {
|
|
355
|
-
return this.#
|
|
350
|
+
return this.#chuiAt.canPlayType(type);
|
|
356
351
|
}
|
|
357
352
|
getSrc() {
|
|
358
|
-
return this.#
|
|
353
|
+
return this.#chuiAt.getAttribute("src") ?? "";
|
|
359
354
|
}
|
|
360
355
|
setSrc(url = String()) {
|
|
361
|
-
this.#
|
|
362
|
-
this.#
|
|
356
|
+
this.#chuiAt.setAttribute("src", url);
|
|
357
|
+
this.#chuiAt.load();
|
|
363
358
|
return this;
|
|
364
359
|
}
|
|
365
360
|
getSrcObject() {
|
|
366
|
-
return this.#
|
|
361
|
+
return this.#chuiAt.srcObject;
|
|
367
362
|
}
|
|
368
363
|
/** Живой поток — штатное `srcObject`, а не путь */
|
|
369
364
|
setSrcObject(object) {
|
|
370
|
-
this.#
|
|
365
|
+
this.#chuiAt.srcObject = object;
|
|
371
366
|
return this;
|
|
372
367
|
}
|
|
373
368
|
getCurrentTime() {
|
|
374
|
-
return this.#
|
|
369
|
+
return this.#chuiAt.currentTime;
|
|
375
370
|
}
|
|
376
371
|
setCurrentTime(seconds = Number()) {
|
|
377
|
-
this.#
|
|
372
|
+
this.#chuiAt.currentTime = Number(seconds);
|
|
378
373
|
// Подписи и заливка не ждут `timeupdate`: перемотка из кода видна сразу
|
|
379
374
|
this.#renderProgress();
|
|
380
375
|
return this;
|
|
381
376
|
}
|
|
382
377
|
getDuration() {
|
|
383
|
-
return this.#
|
|
378
|
+
return this.#chuiAt.duration;
|
|
384
379
|
}
|
|
385
380
|
getBuffered() {
|
|
386
|
-
return this.#
|
|
381
|
+
return this.#chuiAt.buffered;
|
|
387
382
|
}
|
|
388
383
|
getPaused() {
|
|
389
|
-
return this.#
|
|
384
|
+
return this.#chuiAt.paused;
|
|
390
385
|
}
|
|
391
386
|
getEnded() {
|
|
392
|
-
return this.#
|
|
387
|
+
return this.#chuiAt.ended;
|
|
393
388
|
}
|
|
394
389
|
getReadyState() {
|
|
395
|
-
return this.#
|
|
390
|
+
return this.#chuiAt.readyState;
|
|
396
391
|
}
|
|
397
392
|
getVolume() {
|
|
398
|
-
return this.#
|
|
393
|
+
return this.#chuiAt.volume;
|
|
399
394
|
}
|
|
400
395
|
setVolume(value = Number()) {
|
|
401
|
-
this.#
|
|
396
|
+
this.#chuiAt.volume = Math.min(1, Math.max(0, Number(value)));
|
|
402
397
|
return this;
|
|
403
398
|
}
|
|
404
399
|
getMuted() {
|
|
405
|
-
return this.#
|
|
400
|
+
return this.#chuiAt.muted;
|
|
406
401
|
}
|
|
407
402
|
setMuted(boolean = Boolean()) {
|
|
408
|
-
this.#
|
|
403
|
+
this.#chuiAt.muted = boolean === true;
|
|
409
404
|
return this;
|
|
410
405
|
}
|
|
411
406
|
getPlaybackRate() {
|
|
412
|
-
return this.#
|
|
407
|
+
return this.#chuiAt.playbackRate;
|
|
413
408
|
}
|
|
414
409
|
setPlaybackRate(rate = Number()) {
|
|
415
|
-
this.#
|
|
410
|
+
this.#chuiAt.playbackRate = Number(rate);
|
|
416
411
|
return this;
|
|
417
412
|
}
|
|
418
413
|
getDefaultPlaybackRate() {
|
|
419
|
-
return this.#
|
|
414
|
+
return this.#chuiAt.defaultPlaybackRate;
|
|
420
415
|
}
|
|
421
416
|
setDefaultPlaybackRate(rate = Number()) {
|
|
422
|
-
this.#
|
|
417
|
+
this.#chuiAt.defaultPlaybackRate = Number(rate);
|
|
423
418
|
return this;
|
|
424
419
|
}
|
|
425
420
|
getLoop() {
|
|
426
|
-
return this.#
|
|
421
|
+
return this.#chuiAt.loop;
|
|
427
422
|
}
|
|
428
423
|
setLoop(boolean = Boolean()) {
|
|
429
|
-
this.#
|
|
424
|
+
this.#chuiAt.loop = boolean === true;
|
|
430
425
|
return this;
|
|
431
426
|
}
|
|
432
427
|
getAutoplay() {
|
|
433
|
-
return this.#
|
|
428
|
+
return this.#chuiAt.autoplay;
|
|
434
429
|
}
|
|
435
430
|
setAutoplay(boolean = Boolean()) {
|
|
436
|
-
this.#
|
|
431
|
+
this.#chuiAt.autoplay = boolean === true;
|
|
437
432
|
return this;
|
|
438
433
|
}
|
|
439
434
|
addEventListener(type = String(), listener = () => {}) {
|
|
440
|
-
this.#
|
|
435
|
+
this.#chuiAt.addEventListener(type, listener);
|
|
441
436
|
return this;
|
|
442
437
|
}
|
|
443
438
|
removeEventListener(type = String(), listener = () => {}) {
|
|
444
|
-
this.#
|
|
439
|
+
this.#chuiAt.removeEventListener(type, listener);
|
|
445
440
|
return this;
|
|
446
441
|
}
|
|
447
442
|
set() {
|
|
448
|
-
return this.#
|
|
443
|
+
return this.#chuiApMain;
|
|
449
444
|
}
|
|
450
445
|
detach() {
|
|
451
446
|
this.pause();
|
|
452
|
-
this.#
|
|
447
|
+
this.#chuiApMain.remove();
|
|
453
448
|
return this;
|
|
454
449
|
}
|
|
455
450
|
/** Остановить плеер и снять всё, что живёт вне его узлов. Повторный вызов безопасен */
|
|
456
451
|
destroy() {
|
|
457
452
|
if (this.#destroyed) return this;
|
|
458
453
|
this.#destroyed = true;
|
|
459
|
-
this.#
|
|
460
|
-
this.#
|
|
461
|
-
this.#
|
|
454
|
+
this.#chuiAt.pause();
|
|
455
|
+
this.#chuiAt.removeAttribute("src");
|
|
456
|
+
this.#chuiAt.load(); // отпускает декодер и прочитанный файл
|
|
462
457
|
this.#unsetMediaActions();
|
|
463
458
|
document.removeEventListener("mousedown", this.#onFxOutside);
|
|
464
|
-
this.#
|
|
465
|
-
if (this.#
|
|
459
|
+
this.#chuiAudioFx.destroy();
|
|
460
|
+
if (this.#chuiApMain.parentElement !== null) this.#chuiApMain.remove();
|
|
466
461
|
return this;
|
|
467
462
|
}
|
|
468
463
|
static #ERRORS = {
|
|
@@ -473,11 +468,11 @@ class Audio {
|
|
|
473
468
|
};
|
|
474
469
|
}
|
|
475
470
|
class AudioFX {
|
|
476
|
-
#
|
|
477
|
-
#
|
|
478
|
-
#
|
|
479
|
-
#
|
|
480
|
-
#
|
|
471
|
+
#chuiApEqualizerMain = document.createElement("chui_ap_equalizer_main");
|
|
472
|
+
#chuiApEqualizerControls = document.createElement("chui_ap_equalizer_controls");
|
|
473
|
+
#chuiApEqualizerBlock = document.createElement("chui_ap_equalizer_block");
|
|
474
|
+
#chuiApEqualizerPreampBlock = document.createElement("chui_ap_equalizer_preamp_block");
|
|
475
|
+
#chuiApEqualizerBandBlock = document.createElement("chui_ap_equalizer_band_block");
|
|
481
476
|
/* Поле пресетов и переключатель — общекитовые `Select` и `Toggle`, вложенные в панель:
|
|
482
477
|
клавиатура, роли и состояния у них уже есть, а размеры для полосы панели задаёт
|
|
483
478
|
`audio_styles.css`. Прежде тут были свои элементы, и список пресетов ломался:
|
|
@@ -490,14 +485,14 @@ class AudioFX {
|
|
|
490
485
|
#nodes = new Map();
|
|
491
486
|
#media = undefined;
|
|
492
487
|
#filters = undefined;
|
|
493
|
-
#
|
|
494
|
-
#
|
|
488
|
+
#fxStatus = "chuijs.framework.settings.fx_status";
|
|
489
|
+
#fxPreset = "chuijs.framework.settings.fx_preset";
|
|
495
490
|
#audioContext = new AudioContext();
|
|
496
491
|
#destroyed = false;
|
|
497
492
|
constructor(audio) {
|
|
498
493
|
this.#media = this.#audioContext.createMediaElementSource(audio);
|
|
499
494
|
this.#filters = this.#eqBands.map((band, i) => {
|
|
500
|
-
|
|
495
|
+
const filter = this.#audioContext.createBiquadFilter();
|
|
501
496
|
if (i === 0) {
|
|
502
497
|
filter.type = "lowshelf";
|
|
503
498
|
} else if (i === this.#eqBands.length - 1) {
|
|
@@ -517,8 +512,8 @@ class AudioFX {
|
|
|
517
512
|
this.#media.connect(this.#filters[0]);
|
|
518
513
|
this.#filters[this.#filters.length - 1].connect(this.#audioContext.destination);
|
|
519
514
|
//
|
|
520
|
-
this.#
|
|
521
|
-
this.#filters.forEach((filter) => this.#
|
|
515
|
+
this.#chuiApEqualizerPreampBlock.appendChild(this.#setSliderPreamp(this.#filters));
|
|
516
|
+
this.#filters.forEach((filter) => this.#chuiApEqualizerBandBlock.appendChild(this.#setSliderBand(filter)));
|
|
522
517
|
// Пресеты: подпись варианта — перевод, значение — имя пресета, поэтому выбор
|
|
523
518
|
// читается из события списка, а не сверкой подписи
|
|
524
519
|
this.#presets.addOptions(
|
|
@@ -533,18 +528,18 @@ class AudioFX {
|
|
|
533
528
|
this.#presets.addValueChangeListener((event) => this.#usePreset(event.detail.value));
|
|
534
529
|
// Переключатель — общекитовый `Toggle`; подпись он отдаёт в `aria-label`
|
|
535
530
|
this.#switch.addChangeListener(() => {
|
|
536
|
-
if (this.#switch.getValue()) this.#
|
|
537
|
-
else this.#
|
|
531
|
+
if (this.#switch.getValue()) this.#fxON(this.#audioContext);
|
|
532
|
+
else this.#fxOFF(this.#audioContext);
|
|
538
533
|
});
|
|
539
534
|
//
|
|
540
|
-
this.#
|
|
541
|
-
this.#
|
|
535
|
+
this.#chuiApEqualizerControls.appendChild(this.#presets.set());
|
|
536
|
+
this.#chuiApEqualizerControls.appendChild(this.#switch.set());
|
|
542
537
|
//
|
|
543
|
-
this.#
|
|
544
|
-
this.#
|
|
538
|
+
this.#chuiApEqualizerBlock.appendChild(this.#chuiApEqualizerPreampBlock);
|
|
539
|
+
this.#chuiApEqualizerBlock.appendChild(this.#chuiApEqualizerBandBlock);
|
|
545
540
|
//
|
|
546
|
-
this.#
|
|
547
|
-
this.#
|
|
541
|
+
this.#chuiApEqualizerMain.appendChild(this.#chuiApEqualizerControls);
|
|
542
|
+
this.#chuiApEqualizerMain.appendChild(this.#chuiApEqualizerBlock);
|
|
548
543
|
// По умолчанию эквалайзер выключен — и вид полосы должен говорить то же: пока
|
|
549
544
|
// `restoreFX()` не позвал хозяин, выключатель выключен, а дорожки уже серые
|
|
550
545
|
this.#setStatusBlock(false);
|
|
@@ -558,7 +553,7 @@ class AudioFX {
|
|
|
558
553
|
const known = AudioFX.PRESETS.some((preset) => preset.name === name);
|
|
559
554
|
this.#presets.setValue(known ? name : AudioFX.PRESETS[0].name, true);
|
|
560
555
|
}
|
|
561
|
-
#
|
|
556
|
+
#fxON(audioContext) {
|
|
562
557
|
try {
|
|
563
558
|
this.#media.disconnect(audioContext.destination);
|
|
564
559
|
this.#media.connect(this.#filters[0]);
|
|
@@ -567,25 +562,25 @@ class AudioFX {
|
|
|
567
562
|
this.#media.connect(this.#filters[0]);
|
|
568
563
|
this.#filters[this.#filters.length - 1].connect(audioContext.destination);
|
|
569
564
|
}
|
|
570
|
-
store.delete(this.#
|
|
571
|
-
store.set(this.#
|
|
565
|
+
store.delete(this.#fxStatus);
|
|
566
|
+
store.set(this.#fxStatus, true);
|
|
572
567
|
this.#setStatusBlock(true);
|
|
573
568
|
}
|
|
574
|
-
#
|
|
569
|
+
#fxOFF(audioContext) {
|
|
575
570
|
this.#filters[this.#filters.length - 1].disconnect(audioContext.destination);
|
|
576
571
|
this.#media.disconnect(this.#filters[0]);
|
|
577
572
|
this.#media.connect(audioContext.destination);
|
|
578
|
-
store.delete(this.#
|
|
579
|
-
store.set(this.#
|
|
573
|
+
store.delete(this.#fxStatus);
|
|
574
|
+
store.set(this.#fxStatus, false);
|
|
580
575
|
this.#setStatusBlock(false);
|
|
581
576
|
}
|
|
582
577
|
#setPreset(filter, name) {
|
|
583
|
-
store.delete(this.#
|
|
584
|
-
store.set(this.#
|
|
585
|
-
|
|
586
|
-
this.#renderPreampSlider(undefined,
|
|
587
|
-
|
|
588
|
-
this.#renderSlider(input, filter,
|
|
578
|
+
store.delete(this.#fxPreset);
|
|
579
|
+
store.set(this.#fxPreset, name);
|
|
580
|
+
const preset = this.#getPreset(name);
|
|
581
|
+
this.#renderPreampSlider(undefined, preset, filter);
|
|
582
|
+
const input = preset.inputs.filter((input) => input.id === String(filter.frequency.value))[0];
|
|
583
|
+
this.#renderSlider(input, filter, preset.preamp);
|
|
589
584
|
}
|
|
590
585
|
#setStatusBlock(status) {
|
|
591
586
|
//
|
|
@@ -594,30 +589,30 @@ class AudioFX {
|
|
|
594
589
|
// состоянии не шлёт `change` — включить было бы нечем)
|
|
595
590
|
this.#presets.setDisabled(!status);
|
|
596
591
|
//
|
|
597
|
-
for (
|
|
598
|
-
|
|
592
|
+
for (const band of this.#eqBands) {
|
|
593
|
+
const sliderBand = this.#node(String(band));
|
|
599
594
|
sliderBand.disabled = !status;
|
|
600
595
|
if (status) {
|
|
601
596
|
sliderBand.style.cursor = "pointer";
|
|
602
|
-
this.#
|
|
603
|
-
this.#
|
|
597
|
+
this.#chuiApEqualizerPreampBlock.classList.remove("chui_ap_equalizer_disabled");
|
|
598
|
+
this.#chuiApEqualizerBandBlock.classList.remove("chui_ap_equalizer_disabled");
|
|
604
599
|
} else {
|
|
605
600
|
sliderBand.style.cursor = "not-allowed";
|
|
606
|
-
this.#
|
|
607
|
-
this.#
|
|
601
|
+
this.#chuiApEqualizerPreampBlock.classList.add("chui_ap_equalizer_disabled");
|
|
602
|
+
this.#chuiApEqualizerBandBlock.classList.add("chui_ap_equalizer_disabled");
|
|
608
603
|
}
|
|
609
604
|
}
|
|
610
605
|
//
|
|
611
|
-
|
|
606
|
+
const sliderPreamp = this.#node("preamp");
|
|
612
607
|
sliderPreamp.disabled = !status;
|
|
613
608
|
if (status) {
|
|
614
609
|
sliderPreamp.style.cursor = "pointer";
|
|
615
|
-
this.#
|
|
616
|
-
this.#
|
|
610
|
+
this.#chuiApEqualizerPreampBlock.classList.remove("chui_ap_equalizer_disabled");
|
|
611
|
+
this.#chuiApEqualizerBandBlock.classList.remove("chui_ap_equalizer_disabled");
|
|
617
612
|
} else {
|
|
618
613
|
sliderPreamp.style.cursor = "not-allowed";
|
|
619
|
-
this.#
|
|
620
|
-
this.#
|
|
614
|
+
this.#chuiApEqualizerPreampBlock.classList.add("chui_ap_equalizer_disabled");
|
|
615
|
+
this.#chuiApEqualizerBandBlock.classList.add("chui_ap_equalizer_disabled");
|
|
621
616
|
}
|
|
622
617
|
}
|
|
623
618
|
#getPreset(name) {
|
|
@@ -628,10 +623,10 @@ class AudioFX {
|
|
|
628
623
|
return this.#nodes.get(id);
|
|
629
624
|
}
|
|
630
625
|
#setSliderPreamp(filters) {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
626
|
+
const sliderMain = document.createElement("chui_eq_slider_main");
|
|
627
|
+
const val = document.createElement("slider_label");
|
|
628
|
+
const slider = document.createElement("input");
|
|
629
|
+
const label = document.createElement("slider_label");
|
|
635
630
|
sliderMain.id = "preamp_main";
|
|
636
631
|
slider.id = "preamp";
|
|
637
632
|
slider.type = "range";
|
|
@@ -644,9 +639,8 @@ class AudioFX {
|
|
|
644
639
|
slider.setAttribute("aria-valuetext", I18n.t("media.decibels", { value: 0 }));
|
|
645
640
|
// Доля заливки рельса: заливка рисуется градиентом по тому же числу, что у полос
|
|
646
641
|
// прогресса и громкости
|
|
647
|
-
slider.style.setProperty("--
|
|
648
|
-
slider.oninput = (e) =>
|
|
649
|
-
this.#renderPreampSlider(e.target, this.#getPreset(store.get(this.#fx_preset)), filters);
|
|
642
|
+
slider.style.setProperty("--fx_before_width", `50%`);
|
|
643
|
+
slider.oninput = (e) => this.#renderPreampSlider(e.target, this.#getPreset(store.get(this.#fxPreset)), filters);
|
|
650
644
|
val.id = "val_preamp";
|
|
651
645
|
this.#nodes.set("preamp", slider);
|
|
652
646
|
this.#nodes.set("val_preamp", val);
|
|
@@ -658,27 +652,27 @@ class AudioFX {
|
|
|
658
652
|
return sliderMain;
|
|
659
653
|
}
|
|
660
654
|
#setSliderBand(filter) {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
slider.id =
|
|
655
|
+
const freqValue = String(filter.frequency.value);
|
|
656
|
+
const sliderMain = document.createElement("chui_eq_slider_main");
|
|
657
|
+
const val = document.createElement("slider_label");
|
|
658
|
+
const slider = document.createElement("input");
|
|
659
|
+
const label = document.createElement("slider_label");
|
|
660
|
+
slider.id = freqValue;
|
|
667
661
|
slider.type = "range";
|
|
668
662
|
slider.className = "eq_slider_test";
|
|
669
663
|
slider.step = "0.1";
|
|
670
664
|
slider.min = "-10";
|
|
671
665
|
slider.max = "10";
|
|
672
666
|
slider.value = String(filter.gain.value);
|
|
673
|
-
slider.setAttribute("aria-label", I18n.t("media.hertzGain", { freq:
|
|
667
|
+
slider.setAttribute("aria-label", I18n.t("media.hertzGain", { freq: freqValue }));
|
|
674
668
|
slider.setAttribute("aria-valuetext", I18n.t("media.decibels", { value: slider.value }));
|
|
675
|
-
slider.style.setProperty("--
|
|
669
|
+
slider.style.setProperty("--fx_before_width", `50%`);
|
|
676
670
|
slider.oninput = (e) => this.#renderSlider(e.target, filter, Number(this.#node("preamp").value));
|
|
677
|
-
val.id = "val_" +
|
|
678
|
-
this.#nodes.set(
|
|
679
|
-
this.#nodes.set("val_" +
|
|
671
|
+
val.id = "val_" + freqValue;
|
|
672
|
+
this.#nodes.set(freqValue, slider);
|
|
673
|
+
this.#nodes.set("val_" + freqValue, val);
|
|
680
674
|
val.innerText = String(slider.value);
|
|
681
|
-
label.innerText =
|
|
675
|
+
label.innerText = freqValue.replace("000", "K");
|
|
682
676
|
sliderMain.appendChild(val);
|
|
683
677
|
sliderMain.appendChild(slider);
|
|
684
678
|
sliderMain.appendChild(label);
|
|
@@ -687,35 +681,35 @@ class AudioFX {
|
|
|
687
681
|
restore() {
|
|
688
682
|
setTimeout(() => {
|
|
689
683
|
if (this.#destroyed) return;
|
|
690
|
-
|
|
691
|
-
|
|
684
|
+
const status = store.get(this.#fxStatus);
|
|
685
|
+
const preset = store.get(this.#fxPreset);
|
|
692
686
|
if (status) {
|
|
693
|
-
this.#
|
|
687
|
+
this.#fxON(this.#audioContext);
|
|
694
688
|
this.#switch.setValue(true);
|
|
695
|
-
this.#setPresetName(store.get(this.#
|
|
689
|
+
this.#setPresetName(store.get(this.#fxPreset));
|
|
696
690
|
this.#setStatusBlock(status);
|
|
697
691
|
} else {
|
|
698
|
-
this.#
|
|
692
|
+
this.#fxOFF(this.#audioContext);
|
|
699
693
|
this.#switch.setValue(false);
|
|
700
694
|
if (status === undefined || preset === undefined) {
|
|
701
695
|
this.#setPresetName("Default");
|
|
702
696
|
} else {
|
|
703
|
-
this.#setPresetName(store.get(this.#
|
|
697
|
+
this.#setPresetName(store.get(this.#fxPreset));
|
|
704
698
|
}
|
|
705
699
|
this.#setStatusBlock(status);
|
|
706
700
|
}
|
|
707
701
|
}, 250);
|
|
708
702
|
}
|
|
709
703
|
set() {
|
|
710
|
-
return this.#
|
|
704
|
+
return this.#chuiApEqualizerMain;
|
|
711
705
|
}
|
|
712
706
|
/** Включить или выключить эквалайзер: тот же путь, что у выключателя в панели */
|
|
713
707
|
setEnabled(boolean = Boolean()) {
|
|
714
708
|
const включён = this.#switch.getValue();
|
|
715
709
|
if (boolean === включён) return this;
|
|
716
710
|
this.#switch.setValue(boolean);
|
|
717
|
-
if (boolean) this.#
|
|
718
|
-
else this.#
|
|
711
|
+
if (boolean) this.#fxON(this.#audioContext);
|
|
712
|
+
else this.#fxOFF(this.#audioContext);
|
|
719
713
|
return this;
|
|
720
714
|
}
|
|
721
715
|
/** Состояние выключателя в панели */
|
|
@@ -735,14 +729,14 @@ class AudioFX {
|
|
|
735
729
|
return this;
|
|
736
730
|
}
|
|
737
731
|
#renderPreampSlider(input, preset, filters = []) {
|
|
738
|
-
|
|
739
|
-
|
|
732
|
+
const preampSlider = this.#node("preamp");
|
|
733
|
+
const preampValue = this.#node("val_preamp");
|
|
740
734
|
if (input === undefined) {
|
|
741
|
-
|
|
735
|
+
preampSlider.value = preset.preamp;
|
|
742
736
|
} else {
|
|
743
|
-
|
|
737
|
+
preampSlider.value = input.value;
|
|
744
738
|
preset.inputs.forEach((inp) => {
|
|
745
|
-
|
|
739
|
+
const slid = this.#node(inp.id);
|
|
746
740
|
filters.forEach((filter) => {
|
|
747
741
|
if (String(inp.id) === String(filter.frequency.value)) {
|
|
748
742
|
filter.gain.value = Number(slid.value) + Number(input.value);
|
|
@@ -750,18 +744,18 @@ class AudioFX {
|
|
|
750
744
|
});
|
|
751
745
|
});
|
|
752
746
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
747
|
+
preampValue.innerText = String(preampSlider.value);
|
|
748
|
+
preampSlider.setAttribute("aria-valuetext", I18n.t("media.decibels", { value: preampSlider.value }));
|
|
749
|
+
preampSlider.style.setProperty("--fx_before_width", `${(50 + preampSlider.value * 5).toFixed(1)}%`);
|
|
756
750
|
}
|
|
757
751
|
#renderSlider(input, filter, preamp) {
|
|
758
|
-
|
|
759
|
-
|
|
752
|
+
const slider = this.#node(input.id);
|
|
753
|
+
const val = this.#node("val_" + input.id);
|
|
760
754
|
slider.value = input.value;
|
|
761
755
|
filter.gain.value = Number(Number(slider.value) + preamp);
|
|
762
756
|
val.innerText = String(slider.value);
|
|
763
757
|
slider.setAttribute("aria-valuetext", I18n.t("media.decibels", { value: slider.value }));
|
|
764
|
-
slider.style.setProperty("--
|
|
758
|
+
slider.style.setProperty("--fx_before_width", `${(50 + slider.value * 5).toFixed(1)}%`);
|
|
765
759
|
}
|
|
766
760
|
// ya.music.Audio.fx
|
|
767
761
|
static PRESETS = [
|