chuijs 4.0.2 → 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 +597 -0
- package/README.md +43 -644
- package/framework/appLayout/default/chui_app_layout.js +602 -496
- package/framework/appLayout/default/global_style.css +288 -97
- package/framework/appLayout/default/main_theme_style.css +188 -101
- package/framework/components/chui_accordion/accordion.js +38 -35
- package/framework/components/chui_accordion/styles.css +13 -12
- package/framework/components/chui_action_sheet/action_sheet.js +93 -64
- package/framework/components/chui_action_sheet/styles.css +12 -5
- 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 +16 -11
- package/framework/components/chui_affix/styles.css +5 -0
- package/framework/components/chui_alert/alert.js +34 -19
- package/framework/components/chui_alert/styles.css +7 -0
- package/framework/components/chui_avatar/avatar.js +36 -35
- package/framework/components/chui_avatar/styles.css +1 -1
- package/framework/components/chui_avatar_group/avatar_group.js +25 -23
- package/framework/components/chui_avatar_group/styles.css +1 -1
- package/framework/components/chui_backtop/backtop.js +60 -21
- package/framework/components/chui_backtop/styles.css +21 -0
- package/framework/components/chui_badge/badge.js +30 -28
- 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/breadcrumb.js +15 -13
- package/framework/components/chui_breadcrumb/styles.css +1 -1
- package/framework/components/chui_button/button.js +52 -36
- package/framework/components/chui_button/styles.css +7 -7
- package/framework/components/chui_calendar/calendar.js +18 -16
- package/framework/components/chui_card/card.js +52 -32
- 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 +140 -79
- package/framework/components/chui_chat/styles.css +18 -7
- package/framework/components/chui_clipboard_history/clipboard_history.js +53 -39
- package/framework/components/chui_clipboard_history/styles.css +13 -9
- package/framework/components/chui_code/code.js +10 -10
- package/framework/components/chui_code/styles.css +4 -4
- package/framework/components/chui_code_editor/code_editor.js +73 -57
- package/framework/components/chui_code_editor/styles.css +6 -5
- package/framework/components/chui_command_palette/command_palette.js +87 -64
- package/framework/components/chui_command_palette/styles.css +5 -5
- package/framework/components/chui_console/console.js +13 -13
- package/framework/components/chui_console/styles.css +6 -3
- package/framework/components/chui_content_block/content_block.js +35 -21
- package/framework/components/chui_content_block/styles.css +2 -2
- package/framework/components/chui_context_menu/context_menu.js +171 -58
- package/framework/components/chui_context_menu/styles.css +82 -8
- package/framework/components/chui_custom_element/custom_element.js +25 -18
- package/framework/components/chui_data_grid/data_grid.js +633 -471
- package/framework/components/chui_data_grid/styles.css +24 -22
- package/framework/components/chui_descriptions/descriptions.js +34 -17
- package/framework/components/chui_descriptions/styles.css +1 -1
- package/framework/components/chui_details/details.js +42 -36
- package/framework/components/chui_details/styles.css +12 -11
- package/framework/components/chui_diff_viewer/diff_viewer.js +172 -147
- package/framework/components/chui_diff_viewer/styles.css +10 -10
- package/framework/components/chui_divider/divider.js +19 -17
- package/framework/components/chui_divider/styles.css +2 -2
- package/framework/components/chui_drawer/drawer.js +63 -38
- package/framework/components/chui_drawer/styles.css +18 -4
- package/framework/components/chui_drop_zone/drop_zone.js +37 -27
- package/framework/components/chui_drop_zone/styles.css +10 -6
- package/framework/components/chui_empty_state/empty_state.js +20 -15
- package/framework/components/chui_fieldset/fieldset.js +40 -23
- package/framework/components/chui_fieldset/styles.css +6 -6
- package/framework/components/chui_file_explorer/file_explorer.js +260 -189
- package/framework/components/chui_file_explorer/styles.css +8 -7
- package/framework/components/chui_form/form.js +40 -28
- package/framework/components/chui_form/styles.css +1 -1
- package/framework/components/chui_gantt/gantt.js +102 -78
- package/framework/components/chui_gantt/styles.css +5 -5
- package/framework/components/chui_graphs/graphs.js +376 -239
- package/framework/components/chui_graphs/styles.css +27 -6
- package/framework/components/chui_hero/hero.js +98 -37
- package/framework/components/chui_hero/styles.css +176 -7
- package/framework/components/chui_html_block/html_block.js +8 -8
- package/framework/components/chui_html_block/styles.css +3 -3
- package/framework/components/chui_hx/hx.js +3 -3
- package/framework/components/chui_hx/styles.css +13 -6
- package/framework/components/chui_icons/icons.js +2115 -2115
- package/framework/components/chui_icons/styles.css +4 -3
- package/framework/components/chui_inputs/chui_check_box/check_box.js +31 -19
- package/framework/components/chui_inputs/chui_check_box/styles.css +30 -21
- package/framework/components/chui_inputs/chui_chips/chips.js +141 -70
- package/framework/components/chui_inputs/chui_chips/styles.css +32 -7
- package/framework/components/chui_inputs/chui_color_picker/color_picker.js +55 -38
- package/framework/components/chui_inputs/chui_color_picker/styles.css +7 -7
- package/framework/components/chui_inputs/chui_combo_box/combo_box.js +127 -109
- package/framework/components/chui_inputs/chui_combo_box/styles.css +23 -21
- package/framework/components/chui_inputs/chui_date/date.js +161 -127
- package/framework/components/chui_inputs/chui_date/styles.css +20 -19
- package/framework/components/chui_inputs/chui_date_range/date_range.js +61 -50
- package/framework/components/chui_inputs/chui_date_range/styles.css +3 -3
- package/framework/components/chui_inputs/chui_datetime/datetime.js +47 -39
- package/framework/components/chui_inputs/chui_datetime/styles.css +3 -3
- package/framework/components/chui_inputs/chui_email/email.js +48 -36
- package/framework/components/chui_inputs/chui_email/styles.css +11 -11
- package/framework/components/chui_inputs/chui_file/file.js +71 -44
- package/framework/components/chui_inputs/chui_file/styles.css +23 -19
- package/framework/components/chui_inputs/chui_hotkey/hotkey.js +101 -76
- package/framework/components/chui_inputs/chui_hotkey/styles.css +6 -6
- package/framework/components/chui_inputs/chui_masked/masked.js +83 -72
- package/framework/components/chui_inputs/chui_masked/styles.css +5 -5
- package/framework/components/chui_inputs/chui_mention/mention.js +87 -40
- package/framework/components/chui_inputs/chui_mention/styles.css +14 -8
- package/framework/components/chui_inputs/chui_multi_combo_box/multi_combo_box.js +242 -212
- package/framework/components/chui_inputs/chui_multi_combo_box/styles.css +23 -15
- package/framework/components/chui_inputs/chui_number/number.js +84 -70
- package/framework/components/chui_inputs/chui_number/styles.css +13 -13
- package/framework/components/chui_inputs/chui_otp/otp.js +71 -62
- package/framework/components/chui_inputs/chui_otp/styles.css +5 -5
- package/framework/components/chui_inputs/chui_password/password.js +72 -56
- package/framework/components/chui_inputs/chui_password/styles.css +15 -15
- package/framework/components/chui_inputs/chui_radio_button/radio_button.js +37 -25
- package/framework/components/chui_inputs/chui_radio_button/styles.css +22 -22
- package/framework/components/chui_inputs/chui_rating/rating.js +54 -50
- package/framework/components/chui_inputs/chui_rating/styles.css +3 -3
- package/framework/components/chui_inputs/chui_select_box/select_box.js +132 -108
- package/framework/components/chui_inputs/chui_select_box/styles.css +21 -21
- package/framework/components/chui_inputs/chui_slider/slider.js +41 -38
- package/framework/components/chui_inputs/chui_slider/styles.css +7 -5
- package/framework/components/chui_inputs/chui_text/styles.css +14 -14
- package/framework/components/chui_inputs/chui_text/text.js +69 -52
- package/framework/components/chui_inputs/chui_text_area/styles.css +14 -14
- package/framework/components/chui_inputs/chui_text_area/text_area.js +59 -36
- package/framework/components/chui_inputs/chui_time/styles.css +18 -20
- package/framework/components/chui_inputs/chui_time/time.js +71 -49
- package/framework/components/chui_inputs/chui_toggle/styles.css +13 -6
- package/framework/components/chui_inputs/chui_toggle/toggle.js +27 -27
- package/framework/components/chui_json_editor/json_editor.js +137 -127
- package/framework/components/chui_json_editor/styles.css +5 -5
- package/framework/components/chui_json_viewer/json_viewer.js +133 -114
- package/framework/components/chui_json_viewer/styles.css +9 -8
- package/framework/components/chui_kanban/kanban.js +78 -60
- package/framework/components/chui_kanban/styles.css +8 -4
- package/framework/components/chui_kbd/kbd.js +12 -10
- package/framework/components/chui_kbd/styles.css +1 -1
- package/framework/components/chui_label/label.js +31 -29
- package/framework/components/chui_label/styles.css +3 -3
- package/framework/components/chui_link/link.js +36 -29
- package/framework/components/chui_link/styles.css +11 -4
- package/framework/components/chui_list/list.js +42 -38
- package/framework/components/chui_list/styles.css +4 -3
- package/framework/components/chui_markdown_editor/markdown_editor.js +125 -109
- package/framework/components/chui_markdown_editor/styles.css +5 -5
- package/framework/components/chui_media/audio.js +457 -364
- package/framework/components/chui_media/audio_styles.css +74 -37
- package/framework/components/chui_media/image.js +99 -64
- package/framework/components/chui_media/image_compare.js +86 -55
- package/framework/components/chui_media/image_compare_styles.css +5 -5
- package/framework/components/chui_media/image_styles.css +4 -4
- package/framework/components/chui_media/video.js +264 -171
- package/framework/components/chui_media/video_styles.css +57 -24
- package/framework/components/chui_menu_bar/menu_bar.js +67 -65
- package/framework/components/chui_menu_bar/styles.css +14 -12
- 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 +81 -56
- package/framework/components/chui_modal/styles.css +3 -2
- package/framework/components/chui_notification/notification.js +107 -69
- package/framework/components/chui_notification/notification_download.js +28 -77
- package/framework/components/chui_notification/notification_download_card.js +115 -0
- package/framework/components/chui_notification/notification_download_progress.css +5 -4
- package/framework/components/chui_notification/notification_download_progress.js +73 -105
- package/framework/components/chui_notification/notification_update.js +44 -37
- package/framework/components/chui_notification/status_plate.css +3 -3
- package/framework/components/chui_notification/status_plate.js +2 -2
- package/framework/components/chui_notification/styles.css +6 -1
- package/framework/components/chui_notification/styles_download.css +2 -2
- package/framework/components/chui_notification/styles_update.css +3 -3
- package/framework/components/chui_org_chart/org_chart.js +101 -62
- package/framework/components/chui_org_chart/styles.css +2 -2
- package/framework/components/chui_pagination/pagination.js +65 -30
- package/framework/components/chui_pagination/styles.css +1 -1
- package/framework/components/chui_paragraph/paragraph.js +2 -2
- package/framework/components/chui_paragraph/styles.css +3 -3
- package/framework/components/chui_popover/popover.js +77 -50
- package/framework/components/chui_popover/styles.css +1 -1
- package/framework/components/chui_popups/popups.js +210 -181
- package/framework/components/chui_popups/styles.css +7 -7
- package/framework/components/chui_print_button/print_button.js +48 -33
- package/framework/components/chui_progress_bar/progress_bar.js +49 -19
- package/framework/components/chui_progress_bar/styles.css +6 -6
- package/framework/components/chui_progress_ring/progress_ring.js +32 -30
- package/framework/components/chui_progress_ring/styles.css +1 -1
- package/framework/components/chui_qrcode/qrcode.js +85 -41
- package/framework/components/chui_query_builder/query_builder.js +263 -170
- package/framework/components/chui_query_builder/styles.css +4 -4
- package/framework/components/chui_radio_group/radio_group.js +36 -26
- package/framework/components/chui_radio_group/styles.css +2 -2
- package/framework/components/chui_result/result.js +41 -31
- package/framework/components/chui_result/styles.css +3 -3
- package/framework/components/chui_scheduler/scheduler.js +126 -104
- package/framework/components/chui_scheduler/styles.css +11 -7
- 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 +90 -27
- package/framework/components/chui_segmented_control/styles.css +32 -6
- package/framework/components/chui_settings/settings.js +160 -87
- package/framework/components/chui_settings/styles.css +4 -4
- package/framework/components/chui_shortcut_help/shortcut_help.js +82 -61
- package/framework/components/chui_sidebar_nav/sidebar_nav.js +132 -53
- package/framework/components/chui_sidebar_nav/styles.css +9 -3
- package/framework/components/chui_skeleton/skeleton.js +29 -24
- package/framework/components/chui_slideshow/slideshow.js +122 -103
- package/framework/components/chui_slideshow/styles.css +22 -16
- package/framework/components/chui_spinner/spinner.js +17 -17
- package/framework/components/chui_spinner/styles.css +0 -1
- package/framework/components/chui_split_button/split_button.js +53 -30
- package/framework/components/chui_split_button/styles.css +22 -6
- package/framework/components/chui_splitter/splitter.js +46 -39
- package/framework/components/chui_splitter/styles.css +2 -2
- package/framework/components/chui_statistic/statistic.js +111 -84
- package/framework/components/chui_status_bar/status_bar.js +11 -9
- package/framework/components/chui_status_bar/styles.css +2 -2
- package/framework/components/chui_stepper/stepper.js +230 -54
- package/framework/components/chui_stepper/styles.css +79 -14
- package/framework/components/chui_sticky_bar/sticky_bar.js +53 -17
- package/framework/components/chui_sticky_bar/styles.css +11 -7
- package/framework/components/chui_tabs/chui_tabs.js +100 -46
- package/framework/components/chui_tabs/styles.css +43 -15
- package/framework/components/chui_text_editor/styles.css +24 -23
- package/framework/components/chui_text_editor/text_editor.js +103 -79
- package/framework/components/chui_text_editor/text_editor_panel.js +428 -367
- package/framework/components/chui_theme_toggle/theme_toggle.js +46 -25
- package/framework/components/chui_timeline/styles.css +3 -1
- package/framework/components/chui_timeline/timeline.js +14 -12
- package/framework/components/chui_toast_queue/styles.css +11 -11
- package/framework/components/chui_toast_queue/toast_queue.js +82 -73
- package/framework/components/chui_toolbar/styles.css +2 -2
- package/framework/components/chui_toolbar/toolbar.js +20 -13
- package/framework/components/chui_tooltip/styles.css +2 -2
- package/framework/components/chui_tooltip/tooltip.js +40 -27
- package/framework/components/chui_tour/styles.css +12 -7
- package/framework/components/chui_tour/tour.js +132 -98
- package/framework/components/chui_transfer/styles.css +8 -7
- package/framework/components/chui_transfer/transfer.js +143 -123
- package/framework/components/chui_tree_table/styles.css +4 -4
- package/framework/components/chui_tree_table/tree_table.js +88 -74
- package/framework/components/chui_tree_view/styles.css +14 -10
- package/framework/components/chui_tree_view/tree_view.js +298 -253
- 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 +41 -33
- package/framework/components/chui_webview/styles.css +2 -2
- package/framework/components/chui_webview/webview.js +64 -52
- 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 +153 -123
- package/framework/modules/chui_animations/animations.js +105 -43
- package/framework/modules/chui_animations/motion.js +120 -0
- package/framework/modules/chui_animations/styles.css +37 -4
- package/framework/modules/chui_bridge/preload.js +36 -25
- package/framework/modules/chui_functions.js +69 -63
- package/framework/modules/chui_highlight/chui_highlight.js +25 -22
- package/framework/modules/chui_highlight/styles.css +13 -10
- package/framework/modules/chui_i18n/chui_i18n.js +12 -10
- package/framework/modules/chui_i18n/locales/a11y.js +5 -5
- package/framework/modules/chui_i18n/locales/app_layout.js +5 -5
- package/framework/modules/chui_i18n/locales/calendar.js +3 -3
- package/framework/modules/chui_i18n/locales/common.js +3 -3
- package/framework/modules/chui_i18n/locales/data.js +36 -26
- package/framework/modules/chui_i18n/locales/editors.js +40 -13
- package/framework/modules/chui_i18n/locales/feedback.js +65 -3
- package/framework/modules/chui_i18n/locales/forms.js +8 -8
- package/framework/modules/chui_i18n/locales/graphics.js +8 -8
- package/framework/modules/chui_i18n/locales/media.js +4 -4
- package/framework/modules/chui_i18n/locales/navigation.js +12 -8
- package/framework/modules/chui_i18n/locales/validate.js +3 -3
- package/framework/modules/chui_json/chui_json.js +11 -11
- package/framework/modules/chui_layout/chui_layout.js +12 -10
- package/framework/modules/chui_logger/chui_logger.js +47 -28
- package/framework/modules/chui_page/page.js +39 -33
- package/framework/modules/chui_palette/palette.js +61 -0
- package/framework/modules/chui_qr/chui_qr.js +179 -88
- package/framework/modules/chui_scrollbar/chui_scrollbar.js +79 -68
- package/framework/modules/chui_scrollbar/styles.css +4 -2
- package/framework/modules/chui_services/chui_channels.js +27 -13
- package/framework/modules/chui_services/chui_services.js +47 -39
- package/framework/modules/chui_services/desktop.js +108 -101
- package/framework/modules/chui_sortable/chui_sortable.js +33 -28
- package/framework/modules/chui_validate/chui_validate.js +10 -7
- package/framework/tray_bar/chui_menu_items.js +136 -46
- package/index.d.ts +5046 -0
- package/index.js +199 -1254
- package/package.json +81 -68
- package/framework/components/telegram_bot/chui_telegram_bot.js +0 -184
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
const fs = require("fs");
|
|
2
|
-
const
|
|
3
|
-
const {setStyles, formatBytes, optionOr} = require("../../modules/chui_functions");
|
|
4
|
-
const {Icon, Icons} = require("../chui_icons/icons");
|
|
5
|
-
const {Breadcrumb} = require("../chui_breadcrumb/breadcrumb");
|
|
6
|
-
const {RovingKeyboard} = require("../../modules/chui_a11y/chui_a11y");
|
|
7
|
-
const {I18n} = require("../../modules/chui_i18n/chui_i18n");
|
|
2
|
+
const nodePath = require("path");
|
|
3
|
+
const { setStyles, formatBytes, optionOr } = require("../../modules/chui_functions");
|
|
4
|
+
const { Icon, Icons } = require("../chui_icons/icons");
|
|
5
|
+
const { Breadcrumb } = require("../chui_breadcrumb/breadcrumb");
|
|
6
|
+
const { RovingKeyboard } = require("../../modules/chui_a11y/chui_a11y");
|
|
7
|
+
const { I18n } = require("../../modules/chui_i18n/chui_i18n");
|
|
8
8
|
|
|
9
9
|
/** Вид записи: папка или файл */
|
|
10
|
-
const TYPE = {FOLDER: "folder", FILE: "file"};
|
|
10
|
+
const TYPE = { FOLDER: "folder", FILE: "file" };
|
|
11
11
|
/** Колонка сортировки */
|
|
12
|
-
const SORT = {NAME: "name", SIZE: "size", MODIFIED: "modified"};
|
|
12
|
+
const SORT = { NAME: "name", SIZE: "size", MODIFIED: "modified" };
|
|
13
13
|
/** Направление сортировки */
|
|
14
|
-
const DIRECTION = {ASC: "asc", DESC: "desc"};
|
|
14
|
+
const DIRECTION = { ASC: "asc", DESC: "desc" };
|
|
15
15
|
|
|
16
16
|
/** Значок по расширению: одна таблица на все виды файлов */
|
|
17
17
|
const ICON_BY_EXTENSION = {
|
|
@@ -20,7 +20,7 @@ const ICON_BY_EXTENSION = {
|
|
|
20
20
|
audio: "mp3 wav ogg flac m4a aac",
|
|
21
21
|
video: "mp4 mkv avi webm mov",
|
|
22
22
|
text: "md txt rtf pdf doc docx odt",
|
|
23
|
-
code: "js mjs ts tsx jsx json css html htm php py rb java c cpp h sh sql yml yaml"
|
|
23
|
+
code: "js mjs ts tsx jsx json css html htm php py rb java c cpp h sh sql yml yaml",
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
/**
|
|
@@ -73,12 +73,12 @@ class FileExplorer {
|
|
|
73
73
|
#up = document.createElement("button");
|
|
74
74
|
#refresh = document.createElement("button");
|
|
75
75
|
#search = document.createElement("input");
|
|
76
|
-
#
|
|
77
|
-
#
|
|
76
|
+
#searchBox = document.createElement("file_explorer_search_box");
|
|
77
|
+
#searchClear = document.createElement("button");
|
|
78
78
|
#hidden = document.createElement("button");
|
|
79
|
-
#
|
|
79
|
+
#pathBar = undefined;
|
|
80
80
|
#grid = document.createElement("file_explorer_grid");
|
|
81
|
-
#
|
|
81
|
+
#headRow = document.createElement("file_explorer_head_row");
|
|
82
82
|
#body = document.createElement("file_explorer_body");
|
|
83
83
|
#empty = document.createElement("file_explorer_empty");
|
|
84
84
|
#error = document.createElement("file_explorer_error");
|
|
@@ -86,68 +86,70 @@ class FileExplorer {
|
|
|
86
86
|
#id = undefined;
|
|
87
87
|
#provider = undefined;
|
|
88
88
|
#path = "";
|
|
89
|
-
#
|
|
89
|
+
#rootPath = undefined;
|
|
90
90
|
#history = [];
|
|
91
|
-
#
|
|
91
|
+
#historyIndex = -1;
|
|
92
92
|
#entries = [];
|
|
93
93
|
#selected = new Set();
|
|
94
94
|
#anchor = undefined;
|
|
95
95
|
#filter = "";
|
|
96
|
-
#sort = {key: SORT.NAME, direction: DIRECTION.ASC};
|
|
97
|
-
#
|
|
96
|
+
#sort = { key: SORT.NAME, direction: DIRECTION.ASC };
|
|
97
|
+
#showHidden = false;
|
|
98
98
|
#loading = false;
|
|
99
|
-
#
|
|
100
|
-
#
|
|
99
|
+
#loadToken = 0;
|
|
100
|
+
#errorObject = undefined;
|
|
101
101
|
#selectable = true;
|
|
102
|
-
#
|
|
103
|
-
#
|
|
102
|
+
#showSize = true;
|
|
103
|
+
#showModified = true;
|
|
104
104
|
#height = "300px";
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
105
|
+
#titleText = undefined;
|
|
106
|
+
#labelText = undefined;
|
|
107
|
+
#emptyText = undefined;
|
|
108
|
+
#activePath = undefined;
|
|
109
|
+
#focusPath = undefined;
|
|
110
110
|
#listeners = [];
|
|
111
|
-
#
|
|
112
|
-
#
|
|
111
|
+
#onOpen = undefined;
|
|
112
|
+
#onSelect = undefined;
|
|
113
113
|
#roving = undefined;
|
|
114
114
|
|
|
115
|
-
constructor(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
115
|
+
constructor(
|
|
116
|
+
options = {
|
|
117
|
+
id: String(),
|
|
118
|
+
title: String(),
|
|
119
|
+
label: String(),
|
|
120
|
+
path: String(),
|
|
121
|
+
root: String(),
|
|
122
|
+
provider: undefined,
|
|
123
|
+
filter: String(),
|
|
124
|
+
sort: Object(),
|
|
125
|
+
showHidden: Boolean,
|
|
126
|
+
selectable: Boolean,
|
|
127
|
+
showSize: Boolean,
|
|
128
|
+
showModified: Boolean,
|
|
129
|
+
height: String(),
|
|
130
|
+
emptyText: String(),
|
|
131
|
+
errorText: String(),
|
|
132
|
+
onOpen: undefined,
|
|
133
|
+
onSelect: undefined,
|
|
134
|
+
changeListener: undefined,
|
|
135
|
+
}
|
|
136
|
+
) {
|
|
135
137
|
setStyles(__dirname + "/styles.css", "chUiJS_FileExplorer");
|
|
136
138
|
FileExplorer.#counter += 1;
|
|
137
139
|
this.#id = options.id === undefined ? `chui_file_explorer_${FileExplorer.#counter}` : options.id;
|
|
138
140
|
this.#root.id = this.#id;
|
|
139
141
|
this.#root.setAttribute("role", "group");
|
|
140
|
-
this.#
|
|
142
|
+
this.#labelText = options.label;
|
|
141
143
|
this.#provider = typeof options.provider === "function" ? options.provider : FileExplorer.localProvider();
|
|
142
|
-
this.#
|
|
144
|
+
this.#rootPath = options.root === undefined ? undefined : String(options.root);
|
|
143
145
|
this.#selectable = options.selectable !== false;
|
|
144
|
-
this.#
|
|
145
|
-
this.#
|
|
146
|
-
this.#
|
|
147
|
-
this.#
|
|
148
|
-
this.#
|
|
149
|
-
if (options.emptyText !== undefined) this.#
|
|
150
|
-
else this.#
|
|
146
|
+
this.#showSize = options.showSize !== false;
|
|
147
|
+
this.#showModified = options.showModified !== false;
|
|
148
|
+
this.#showHidden = options.showHidden === true;
|
|
149
|
+
this.#onOpen = options.onOpen;
|
|
150
|
+
this.#onSelect = options.onSelect;
|
|
151
|
+
if (options.emptyText !== undefined) this.#emptyText = String(options.emptyText);
|
|
152
|
+
else this.#emptyText = I18n.t("fileExplorer.empty");
|
|
151
153
|
if (options.filter !== undefined) this.#filter = String(options.filter);
|
|
152
154
|
this.#title.className = "file_explorer_title";
|
|
153
155
|
this.#title.id = `${this.#id}_title`;
|
|
@@ -157,8 +159,8 @@ class FileExplorer {
|
|
|
157
159
|
this.#actions.className = "file_explorer_actions";
|
|
158
160
|
this.#grid.className = "file_explorer_grid";
|
|
159
161
|
this.#grid.setAttribute("role", "grid");
|
|
160
|
-
this.#
|
|
161
|
-
this.#
|
|
162
|
+
this.#headRow.className = "file_explorer_head_row";
|
|
163
|
+
this.#headRow.setAttribute("role", "row");
|
|
162
164
|
this.#body.className = "file_explorer_body";
|
|
163
165
|
this.#body.setAttribute("role", "rowgroup");
|
|
164
166
|
this.#empty.className = "file_explorer_empty";
|
|
@@ -169,12 +171,12 @@ class FileExplorer {
|
|
|
169
171
|
this.#status.setAttribute("role", "status");
|
|
170
172
|
this.#status.setAttribute("aria-live", "polite");
|
|
171
173
|
this.#buildHead();
|
|
172
|
-
this.#
|
|
173
|
-
this.#
|
|
174
|
+
this.#pathBar = new Breadcrumb({ label: I18n.t("fileExplorer.path") });
|
|
175
|
+
this.#pathBar.set().classList.add("file_explorer_path");
|
|
174
176
|
this.#root.appendChild(this.#title);
|
|
175
177
|
this.#root.appendChild(this.#head);
|
|
176
|
-
this.#root.appendChild(this.#
|
|
177
|
-
this.#grid.appendChild(this.#
|
|
178
|
+
this.#root.appendChild(this.#pathBar.set());
|
|
179
|
+
this.#grid.appendChild(this.#headRow);
|
|
178
180
|
this.#grid.appendChild(this.#body);
|
|
179
181
|
this.#root.appendChild(this.#grid);
|
|
180
182
|
this.#root.appendChild(this.#empty);
|
|
@@ -189,9 +191,9 @@ class FileExplorer {
|
|
|
189
191
|
this.refreshSearch();
|
|
190
192
|
// Сортировка применяется только теперь: `setSort()` перерисовывает список, а строку пути
|
|
191
193
|
// и прочие узлы разметки он застал бы ещё не созданными
|
|
192
|
-
const
|
|
193
|
-
const
|
|
194
|
-
if (
|
|
194
|
+
const sortKey = optionOr(options.sort === undefined ? undefined : options.sort.key, undefined);
|
|
195
|
+
const sortDirection = optionOr(options.sort === undefined ? undefined : options.sort.direction, undefined);
|
|
196
|
+
if (sortKey !== undefined || sortDirection !== undefined) this.setSort(sortKey, sortDirection);
|
|
195
197
|
this.#grid.addEventListener("keydown", this.#onKeyDown);
|
|
196
198
|
this.#grid.addEventListener("click", this.#onClick);
|
|
197
199
|
this.#grid.addEventListener("dblclick", this.#onDoubleClick);
|
|
@@ -204,9 +206,9 @@ class FileExplorer {
|
|
|
204
206
|
/** Провайдер по умолчанию: настоящая файловая система */
|
|
205
207
|
static localProvider() {
|
|
206
208
|
return (path) => {
|
|
207
|
-
const list = fs.readdirSync(String(path), {withFileTypes: true});
|
|
209
|
+
const list = fs.readdirSync(String(path), { withFileTypes: true });
|
|
208
210
|
return list.map((entry) => {
|
|
209
|
-
const full =
|
|
211
|
+
const full = nodePath.join(String(path), entry.name);
|
|
210
212
|
let size;
|
|
211
213
|
let modified;
|
|
212
214
|
try {
|
|
@@ -224,7 +226,7 @@ class FileExplorer {
|
|
|
224
226
|
type: entry.isDirectory() ? TYPE.FOLDER : TYPE.FILE,
|
|
225
227
|
size: size,
|
|
226
228
|
modified: modified,
|
|
227
|
-
hidden: entry.name.startsWith(".")
|
|
229
|
+
hidden: entry.name.startsWith("."),
|
|
228
230
|
};
|
|
229
231
|
});
|
|
230
232
|
};
|
|
@@ -236,11 +238,11 @@ class FileExplorer {
|
|
|
236
238
|
}
|
|
237
239
|
|
|
238
240
|
getTitle() {
|
|
239
|
-
return this.#
|
|
241
|
+
return this.#titleText;
|
|
240
242
|
}
|
|
241
243
|
|
|
242
244
|
getLabel() {
|
|
243
|
-
return this.#
|
|
245
|
+
return this.#labelText;
|
|
244
246
|
}
|
|
245
247
|
|
|
246
248
|
/** Текущая папка */
|
|
@@ -250,7 +252,7 @@ class FileExplorer {
|
|
|
250
252
|
|
|
251
253
|
/** Корень навигации (`undefined` — подниматься можно до корня пути) */
|
|
252
254
|
getRoot() {
|
|
253
|
-
return this.#
|
|
255
|
+
return this.#rootPath;
|
|
254
256
|
}
|
|
255
257
|
|
|
256
258
|
/** Все загруженные записи текущей папки (со скрытыми), в исходном порядке провайдера */
|
|
@@ -282,11 +284,11 @@ class FileExplorer {
|
|
|
282
284
|
}
|
|
283
285
|
|
|
284
286
|
getSort() {
|
|
285
|
-
return {key: this.#sort.key, direction: this.#sort.direction};
|
|
287
|
+
return { key: this.#sort.key, direction: this.#sort.direction };
|
|
286
288
|
}
|
|
287
289
|
|
|
288
290
|
isShowHidden() {
|
|
289
|
-
return this.#
|
|
291
|
+
return this.#showHidden;
|
|
290
292
|
}
|
|
291
293
|
|
|
292
294
|
isSelectable() {
|
|
@@ -302,33 +304,33 @@ class FileExplorer {
|
|
|
302
304
|
}
|
|
303
305
|
|
|
304
306
|
getEmptyText() {
|
|
305
|
-
return this.#
|
|
307
|
+
return this.#emptyText;
|
|
306
308
|
}
|
|
307
309
|
|
|
308
310
|
/** Текст ошибки чтения (пустая строка, если папка прочитана) */
|
|
309
311
|
getErrorText() {
|
|
310
|
-
if (this.#
|
|
311
|
-
const message = this.#
|
|
312
|
-
return I18n.t("fileExplorer.readFailed", {message: message});
|
|
312
|
+
if (this.#errorObject === undefined) return "";
|
|
313
|
+
const message = this.#errorObject instanceof Error ? this.#errorObject.message : String(this.#errorObject);
|
|
314
|
+
return I18n.t("fileExplorer.readFailed", { message: message });
|
|
313
315
|
}
|
|
314
316
|
|
|
315
317
|
/** История переходов и место в ней — для кнопок «назад/вперёд» */
|
|
316
318
|
getHistory() {
|
|
317
|
-
return {paths: [...this.#history], index: this.#
|
|
319
|
+
return { paths: [...this.#history], index: this.#historyIndex };
|
|
318
320
|
}
|
|
319
321
|
|
|
320
322
|
canGoBack() {
|
|
321
|
-
return this.#
|
|
323
|
+
return this.#historyIndex > 0;
|
|
322
324
|
}
|
|
323
325
|
|
|
324
326
|
canGoForward() {
|
|
325
|
-
return this.#
|
|
327
|
+
return this.#historyIndex >= 0 && this.#historyIndex < this.#history.length - 1;
|
|
326
328
|
}
|
|
327
329
|
|
|
328
330
|
/** Выше корня (или выше самого верхнего уровня пути) подняться нельзя */
|
|
329
331
|
canGoUp() {
|
|
330
332
|
if (this.#path === "") return false;
|
|
331
|
-
if (this.#
|
|
333
|
+
if (this.#rootPath !== undefined && this.#path === this.#rootPath.replace(/[\\/]+$/, "")) return false;
|
|
332
334
|
return FileExplorer.#parentOf(this.#path) !== undefined;
|
|
333
335
|
}
|
|
334
336
|
|
|
@@ -342,7 +344,7 @@ class FileExplorer {
|
|
|
342
344
|
}
|
|
343
345
|
|
|
344
346
|
setTitle(text = undefined) {
|
|
345
|
-
this.#
|
|
347
|
+
this.#titleText = text;
|
|
346
348
|
if (text === undefined || text === "") {
|
|
347
349
|
this.#title.hidden = true;
|
|
348
350
|
this.#title.innerText = "";
|
|
@@ -356,7 +358,7 @@ class FileExplorer {
|
|
|
356
358
|
}
|
|
357
359
|
|
|
358
360
|
setLabel(text = undefined) {
|
|
359
|
-
this.#
|
|
361
|
+
this.#labelText = text;
|
|
360
362
|
this.#syncLabel();
|
|
361
363
|
return this;
|
|
362
364
|
}
|
|
@@ -368,7 +370,7 @@ class FileExplorer {
|
|
|
368
370
|
}
|
|
369
371
|
|
|
370
372
|
setEmptyText(text = String()) {
|
|
371
|
-
this.#
|
|
373
|
+
this.#emptyText = text === undefined ? "" : String(text);
|
|
372
374
|
this.#render();
|
|
373
375
|
return this;
|
|
374
376
|
}
|
|
@@ -400,14 +402,17 @@ class FileExplorer {
|
|
|
400
402
|
if (column !== undefined && Object.values(SORT).includes(column)) this.#sort.key = column;
|
|
401
403
|
if (order !== undefined && Object.values(DIRECTION).includes(order)) this.#sort.direction = order;
|
|
402
404
|
// До конца сборки разметки перерисовывать нечего (вызов из конструктора)
|
|
403
|
-
if (this.#
|
|
405
|
+
if (this.#pathBar !== undefined) this.#render();
|
|
404
406
|
return this;
|
|
405
407
|
}
|
|
406
408
|
|
|
407
409
|
setShowHidden(shown = Boolean()) {
|
|
408
|
-
this.#
|
|
409
|
-
this.#hidden.setAttribute("aria-pressed", this.#
|
|
410
|
-
this.#hidden.setAttribute(
|
|
410
|
+
this.#showHidden = shown === true;
|
|
411
|
+
this.#hidden.setAttribute("aria-pressed", this.#showHidden ? "true" : "false");
|
|
412
|
+
this.#hidden.setAttribute(
|
|
413
|
+
"aria-label",
|
|
414
|
+
this.#showHidden ? I18n.t("fileExplorer.hideHidden") : I18n.t("fileExplorer.showHidden")
|
|
415
|
+
);
|
|
411
416
|
this.#render();
|
|
412
417
|
this.#announceState();
|
|
413
418
|
return this;
|
|
@@ -416,7 +421,9 @@ class FileExplorer {
|
|
|
416
421
|
setSelected(paths = Array(), silent = false) {
|
|
417
422
|
if (!this.#selectable) return this;
|
|
418
423
|
const allowed = new Set(this.#entries.map((entry) => entry.path));
|
|
419
|
-
this.#selected = new Set(
|
|
424
|
+
this.#selected = new Set(
|
|
425
|
+
(Array.isArray(paths) ? paths : []).map((path) => String(path)).filter((path) => allowed.has(path))
|
|
426
|
+
);
|
|
420
427
|
this.#anchor = this.#selected.size === 0 ? undefined : [...this.#selected][this.#selected.size - 1];
|
|
421
428
|
this.#applySelection();
|
|
422
429
|
if (silent === false) this.#notifySelect();
|
|
@@ -431,8 +438,8 @@ class FileExplorer {
|
|
|
431
438
|
open(entry = Object()) {
|
|
432
439
|
if (entry === undefined || entry === null) return this;
|
|
433
440
|
if (entry.type === TYPE.FOLDER) return this.go(entry.path);
|
|
434
|
-
this.#announce(I18n.t("fileExplorer.openedFile", {name: entry.name}));
|
|
435
|
-
if (this.#
|
|
441
|
+
this.#announce(I18n.t("fileExplorer.openedFile", { name: entry.name }));
|
|
442
|
+
if (this.#onOpen !== undefined) this.#onOpen(Object.assign({}, entry), this);
|
|
436
443
|
return this;
|
|
437
444
|
}
|
|
438
445
|
|
|
@@ -443,35 +450,37 @@ class FileExplorer {
|
|
|
443
450
|
}
|
|
444
451
|
|
|
445
452
|
/** Загрузка папки: промис разрешается, когда данные пришли (устаревший ответ отбрасывается) */
|
|
446
|
-
async load(path = String(), options = {history: true, keepSelection: false}) {
|
|
453
|
+
async load(path = String(), options = { history: true, keepSelection: false }) {
|
|
447
454
|
const target = path === undefined || path === null || path === "" ? this.#path : String(path);
|
|
448
|
-
const token = this.#
|
|
449
|
-
this.#
|
|
455
|
+
const token = this.#loadToken + 1;
|
|
456
|
+
this.#loadToken = token;
|
|
450
457
|
this.#loading = true;
|
|
451
458
|
this.#grid.setAttribute("aria-busy", "true");
|
|
452
|
-
this.#status.innerText = I18n.t("fileExplorer.loading", {target: target});
|
|
459
|
+
this.#status.innerText = I18n.t("fileExplorer.loading", { target: target });
|
|
453
460
|
let list;
|
|
454
461
|
try {
|
|
455
462
|
list = await Promise.resolve(this.#provider(target));
|
|
456
463
|
} catch (error) {
|
|
457
|
-
if (token !== this.#
|
|
464
|
+
if (token !== this.#loadToken) return this;
|
|
458
465
|
this.#loading = false;
|
|
459
466
|
this.#entries = [];
|
|
460
|
-
this.#
|
|
467
|
+
this.#errorObject = error;
|
|
461
468
|
this.#path = target;
|
|
462
469
|
this.#render();
|
|
463
|
-
this.#announce(
|
|
470
|
+
this.#announce(
|
|
471
|
+
I18n.t("fileExplorer.readFailed", { message: error instanceof Error ? error.message : String(error) })
|
|
472
|
+
);
|
|
464
473
|
return this;
|
|
465
474
|
}
|
|
466
|
-
if (token !== this.#
|
|
475
|
+
if (token !== this.#loadToken) return this;
|
|
467
476
|
this.#entries = FileExplorer.#normalize(list, target);
|
|
468
|
-
this.#
|
|
477
|
+
this.#errorObject = undefined;
|
|
469
478
|
this.#loading = false;
|
|
470
479
|
this.#path = target;
|
|
471
480
|
if (options.keepSelection !== true) this.#selected.clear();
|
|
472
481
|
if (options.history !== false) {
|
|
473
|
-
this.#history = this.#history.slice(0, this.#
|
|
474
|
-
this.#
|
|
482
|
+
this.#history = this.#history.slice(0, this.#historyIndex + 1).concat([target]);
|
|
483
|
+
this.#historyIndex = this.#history.length - 1;
|
|
475
484
|
}
|
|
476
485
|
this.#render();
|
|
477
486
|
this.#announceState();
|
|
@@ -480,14 +489,14 @@ class FileExplorer {
|
|
|
480
489
|
|
|
481
490
|
back() {
|
|
482
491
|
if (!this.canGoBack()) return this;
|
|
483
|
-
this.#
|
|
484
|
-
return this.load(this.#history[this.#
|
|
492
|
+
this.#historyIndex -= 1;
|
|
493
|
+
return this.load(this.#history[this.#historyIndex], { history: false });
|
|
485
494
|
}
|
|
486
495
|
|
|
487
496
|
forward() {
|
|
488
497
|
if (!this.canGoForward()) return this;
|
|
489
|
-
this.#
|
|
490
|
-
return this.load(this.#history[this.#
|
|
498
|
+
this.#historyIndex += 1;
|
|
499
|
+
return this.load(this.#history[this.#historyIndex], { history: false });
|
|
491
500
|
}
|
|
492
501
|
|
|
493
502
|
/** На уровень выше; выше корня не поднимаемся */
|
|
@@ -498,7 +507,7 @@ class FileExplorer {
|
|
|
498
507
|
|
|
499
508
|
/** Перечитать текущую папку, сохранив выбор */
|
|
500
509
|
refresh() {
|
|
501
|
-
return this.load(this.#path, {history: false, keepSelection: true});
|
|
510
|
+
return this.load(this.#path, { history: false, keepSelection: true });
|
|
502
511
|
}
|
|
503
512
|
|
|
504
513
|
/** Фокус на запись по пути (или на первую видимую) */
|
|
@@ -526,7 +535,8 @@ class FileExplorer {
|
|
|
526
535
|
static #normalize(list = Array(), path = String()) {
|
|
527
536
|
const source = Array.isArray(list) ? list : [];
|
|
528
537
|
return source.map((item, index) => {
|
|
529
|
-
const entry =
|
|
538
|
+
const entry =
|
|
539
|
+
typeof item === "string" ? { name: item } : item === null || typeof item !== "object" ? {} : item;
|
|
530
540
|
const name = entry.name === undefined ? String(index) : String(entry.name);
|
|
531
541
|
return {
|
|
532
542
|
name: name,
|
|
@@ -535,7 +545,7 @@ class FileExplorer {
|
|
|
535
545
|
size: entry.size === undefined ? undefined : Number(entry.size),
|
|
536
546
|
modified: entry.modified === undefined ? undefined : entry.modified,
|
|
537
547
|
hidden: entry.hidden === undefined ? name.startsWith(".") : entry.hidden === true,
|
|
538
|
-
icon: entry.icon
|
|
548
|
+
icon: entry.icon,
|
|
539
549
|
};
|
|
540
550
|
});
|
|
541
551
|
}
|
|
@@ -590,7 +600,8 @@ class FileExplorer {
|
|
|
590
600
|
#compare(a = Object(), b = Object()) {
|
|
591
601
|
const direction = this.#sort.direction === DIRECTION.DESC ? -1 : 1;
|
|
592
602
|
if (a.type !== b.type) return a.type === TYPE.FOLDER ? -1 : 1;
|
|
593
|
-
if (this.#sort.key === SORT.NAME)
|
|
603
|
+
if (this.#sort.key === SORT.NAME)
|
|
604
|
+
return direction * a.name.localeCompare(b.name, "ru", { numeric: true, sensitivity: "base" });
|
|
594
605
|
const value = (entry) => {
|
|
595
606
|
if (this.#sort.key === SORT.SIZE) return entry.size === undefined ? undefined : Number(entry.size);
|
|
596
607
|
if (entry.modified === undefined || entry.modified === null) return undefined;
|
|
@@ -599,7 +610,8 @@ class FileExplorer {
|
|
|
599
610
|
};
|
|
600
611
|
const left = value(a);
|
|
601
612
|
const right = value(b);
|
|
602
|
-
if (left === undefined && right === undefined)
|
|
613
|
+
if (left === undefined && right === undefined)
|
|
614
|
+
return direction * a.name.localeCompare(b.name, "ru", { numeric: true });
|
|
603
615
|
if (left === undefined) return 1;
|
|
604
616
|
if (right === undefined) return -1;
|
|
605
617
|
return direction * (left - right);
|
|
@@ -607,16 +619,29 @@ class FileExplorer {
|
|
|
607
619
|
|
|
608
620
|
#visibleEntries() {
|
|
609
621
|
const filter = this.#filter.trim().toLowerCase();
|
|
610
|
-
const list = this.#entries.filter(
|
|
611
|
-
(
|
|
622
|
+
const list = this.#entries.filter(
|
|
623
|
+
(entry) =>
|
|
624
|
+
(this.#showHidden || entry.hidden !== true) &&
|
|
625
|
+
(filter === "" || entry.name.toLowerCase().includes(filter))
|
|
626
|
+
);
|
|
612
627
|
return list.sort((a, b) => this.#compare(a, b));
|
|
613
628
|
}
|
|
614
629
|
|
|
615
630
|
#buildHead() {
|
|
616
|
-
this.#actions.appendChild(
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
this.#actions.appendChild(
|
|
631
|
+
this.#actions.appendChild(
|
|
632
|
+
this.#navButton(this.#back, Icons.NAVIGATION.ARROW_BACK, I18n.t("a11y.previous"), () => this.back())
|
|
633
|
+
);
|
|
634
|
+
this.#actions.appendChild(
|
|
635
|
+
this.#navButton(this.#forward, Icons.NAVIGATION.ARROW_FORWARD, I18n.t("a11y.next"), () => this.forward())
|
|
636
|
+
);
|
|
637
|
+
this.#actions.appendChild(
|
|
638
|
+
this.#navButton(this.#up, Icons.NAVIGATION.ARROW_UPWARD, I18n.t("fileExplorer.up"), () => this.up())
|
|
639
|
+
);
|
|
640
|
+
this.#actions.appendChild(
|
|
641
|
+
this.#navButton(this.#refresh, Icons.NAVIGATION.REFRESH, I18n.t("fileExplorer.refresh"), () =>
|
|
642
|
+
this.refresh()
|
|
643
|
+
)
|
|
644
|
+
);
|
|
620
645
|
this.#head.appendChild(this.#actions);
|
|
621
646
|
this.#search.className = "file_explorer_search";
|
|
622
647
|
this.#search.type = "search";
|
|
@@ -627,32 +652,38 @@ class FileExplorer {
|
|
|
627
652
|
this.#search.addEventListener("input", () => this.setFilter(this.#search.value));
|
|
628
653
|
// Кнопка очистки — своя: у `input[type=search]` системная кнопка очистки скрыта,
|
|
629
654
|
// чтобы поле выглядело как остальные (см. стили)
|
|
630
|
-
this.#
|
|
631
|
-
this.#
|
|
632
|
-
this.#
|
|
633
|
-
this.#
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
this.#
|
|
637
|
-
this.#
|
|
655
|
+
this.#searchBox.className = "file_explorer_search_box";
|
|
656
|
+
this.#searchClear.type = "button";
|
|
657
|
+
this.#searchClear.className = "file_explorer_search_clear";
|
|
658
|
+
this.#searchClear.setAttribute("aria-label", I18n.t("fileExplorer.clearSearch"));
|
|
659
|
+
const clearGlyph = new Icon(Icons.NAVIGATION.CLOSE, "16px").set();
|
|
660
|
+
clearGlyph.setAttribute("aria-hidden", "true");
|
|
661
|
+
this.#searchClear.appendChild(clearGlyph);
|
|
662
|
+
this.#searchClear.addEventListener("click", () => {
|
|
638
663
|
this.setFilter("");
|
|
639
664
|
this.#search.focus();
|
|
640
665
|
});
|
|
641
|
-
this.#
|
|
642
|
-
this.#
|
|
643
|
-
this.#head.appendChild(this.#
|
|
666
|
+
this.#searchBox.appendChild(this.#search);
|
|
667
|
+
this.#searchBox.appendChild(this.#searchClear);
|
|
668
|
+
this.#head.appendChild(this.#searchBox);
|
|
644
669
|
this.#syncClear();
|
|
645
670
|
this.#hidden.className = "file_explorer_hidden";
|
|
646
671
|
this.#hidden.type = "button";
|
|
647
|
-
this.#hidden.setAttribute("aria-pressed", this.#
|
|
648
|
-
this.#hidden.setAttribute(
|
|
649
|
-
|
|
672
|
+
this.#hidden.setAttribute("aria-pressed", this.#showHidden ? "true" : "false");
|
|
673
|
+
this.#hidden.setAttribute(
|
|
674
|
+
"aria-label",
|
|
675
|
+
this.#showHidden ? I18n.t("fileExplorer.hideHidden") : I18n.t("fileExplorer.showHidden")
|
|
676
|
+
);
|
|
677
|
+
const glyph = new Icon(
|
|
678
|
+
this.#showHidden ? Icons.ACTIONS.VISIBILITY : Icons.ACTIONS.VISIBILITY_OFF,
|
|
679
|
+
"18px"
|
|
680
|
+
).set();
|
|
650
681
|
glyph.setAttribute("aria-hidden", "true");
|
|
651
682
|
this.#hidden.appendChild(glyph);
|
|
652
|
-
this.#hidden.addEventListener("click", () => this.setShowHidden(!this.#
|
|
683
|
+
this.#hidden.addEventListener("click", () => this.setShowHidden(!this.#showHidden));
|
|
653
684
|
this.#head.appendChild(this.#hidden);
|
|
654
685
|
// У панели одна остановка обхода: по кнопкам ходят стрелки/Home/End
|
|
655
|
-
this.#roving = new RovingKeyboard(this.#actions, {itemSelector: "button"});
|
|
686
|
+
this.#roving = new RovingKeyboard(this.#actions, { itemSelector: "button" });
|
|
656
687
|
return this;
|
|
657
688
|
}
|
|
658
689
|
|
|
@@ -672,27 +703,40 @@ class FileExplorer {
|
|
|
672
703
|
const cell = document.createElement("file_explorer_head_cell");
|
|
673
704
|
cell.className = "file_explorer_head_cell";
|
|
674
705
|
cell.setAttribute("role", "columnheader");
|
|
675
|
-
cell.setAttribute(
|
|
676
|
-
|
|
677
|
-
: "none"
|
|
706
|
+
cell.setAttribute(
|
|
707
|
+
"aria-sort",
|
|
708
|
+
this.#sort.key === key ? (this.#sort.direction === DIRECTION.DESC ? "descending" : "ascending") : "none"
|
|
709
|
+
);
|
|
678
710
|
const button = document.createElement("button");
|
|
679
711
|
button.type = "button";
|
|
680
712
|
button.className = "file_explorer_sort";
|
|
681
|
-
button.setAttribute("aria-label", I18n.t("fileExplorer.sortBy", {title: title}));
|
|
713
|
+
button.setAttribute("aria-label", I18n.t("fileExplorer.sortBy", { title: title }));
|
|
682
714
|
const text = document.createElement("file_explorer_sort_title");
|
|
683
715
|
text.innerText = title;
|
|
684
716
|
button.appendChild(text);
|
|
685
717
|
if (this.#sort.key === key) {
|
|
686
|
-
const glyph = new Icon(
|
|
718
|
+
const glyph = new Icon(
|
|
719
|
+
this.#sort.direction === DIRECTION.DESC
|
|
720
|
+
? Icons.NAVIGATION.ARROW_DOWNWARD
|
|
721
|
+
: Icons.NAVIGATION.ARROW_UPWARD,
|
|
722
|
+
"16px"
|
|
723
|
+
).set();
|
|
687
724
|
glyph.setAttribute("aria-hidden", "true");
|
|
688
725
|
button.appendChild(glyph);
|
|
689
726
|
}
|
|
690
727
|
button.addEventListener("click", () => {
|
|
691
728
|
// Повторный клик по активной колонке переворачивает порядок: «без сортировки»
|
|
692
729
|
// у списка файлов смысла не имеет, поэтому цикла из трёх состояний здесь нет
|
|
693
|
-
if (this.#sort.key === key)
|
|
730
|
+
if (this.#sort.key === key)
|
|
731
|
+
this.setSort(key, this.#sort.direction === DIRECTION.ASC ? DIRECTION.DESC : DIRECTION.ASC);
|
|
694
732
|
else this.setSort(key, DIRECTION.ASC);
|
|
695
|
-
this.#announce(
|
|
733
|
+
this.#announce(
|
|
734
|
+
I18n.t("fileExplorer.sorted", {
|
|
735
|
+
title: title,
|
|
736
|
+
direction:
|
|
737
|
+
this.#sort.direction === DIRECTION.ASC ? I18n.t("data.ascending") : I18n.t("data.descending"),
|
|
738
|
+
})
|
|
739
|
+
);
|
|
696
740
|
});
|
|
697
741
|
cell.appendChild(button);
|
|
698
742
|
return cell;
|
|
@@ -713,9 +757,12 @@ class FileExplorer {
|
|
|
713
757
|
return cell;
|
|
714
758
|
}
|
|
715
759
|
cell.classList.add("file_explorer_cell_meta");
|
|
716
|
-
cell.innerText =
|
|
717
|
-
|
|
718
|
-
|
|
760
|
+
cell.innerText =
|
|
761
|
+
key === SORT.SIZE
|
|
762
|
+
? entry.size === undefined
|
|
763
|
+
? ""
|
|
764
|
+
: formatBytes(entry.size)
|
|
765
|
+
: FileExplorer.#dateText(entry.modified);
|
|
719
766
|
return cell;
|
|
720
767
|
}
|
|
721
768
|
|
|
@@ -728,8 +775,8 @@ class FileExplorer {
|
|
|
728
775
|
row.classList.toggle("file_explorer_row_selected", this.#selected.has(entry.path));
|
|
729
776
|
row.classList.toggle("file_explorer_row_folder", entry.type === TYPE.FOLDER);
|
|
730
777
|
row.appendChild(this.#cell(entry, SORT.NAME));
|
|
731
|
-
if (this.#
|
|
732
|
-
if (this.#
|
|
778
|
+
if (this.#showSize) row.appendChild(this.#cell(entry, SORT.SIZE));
|
|
779
|
+
if (this.#showModified) row.appendChild(this.#cell(entry, SORT.MODIFIED));
|
|
733
780
|
return row;
|
|
734
781
|
}
|
|
735
782
|
|
|
@@ -737,27 +784,36 @@ class FileExplorer {
|
|
|
737
784
|
#render() {
|
|
738
785
|
// Строки пересобираются, а фокус живёт на строке: запоминаем её до перерисовки
|
|
739
786
|
const active = document.activeElement;
|
|
740
|
-
const focused =
|
|
741
|
-
|
|
742
|
-
|
|
787
|
+
const focused =
|
|
788
|
+
active === null || active === undefined || !this.#grid.contains(active)
|
|
789
|
+
? null
|
|
790
|
+
: active.closest("file_explorer_row");
|
|
791
|
+
if (focused !== null) this.#focusPath = focused.dataset.path;
|
|
792
|
+
this.#headRow.innerHTML = "";
|
|
743
793
|
this.#body.innerHTML = "";
|
|
744
|
-
this.#
|
|
745
|
-
if (this.#
|
|
746
|
-
if (this.#
|
|
794
|
+
this.#headRow.appendChild(this.#sortButton(SORT.NAME, I18n.t("fileExplorer.columnName")));
|
|
795
|
+
if (this.#showSize) this.#headRow.appendChild(this.#sortButton(SORT.SIZE, I18n.t("fileExplorer.columnSize")));
|
|
796
|
+
if (this.#showModified)
|
|
797
|
+
this.#headRow.appendChild(this.#sortButton(SORT.MODIFIED, I18n.t("fileExplorer.columnModified")));
|
|
747
798
|
// Ширины колонок задаёт компонент: сетка шапки и сетка строк обязаны совпасть,
|
|
748
799
|
// а какие колонки показаны, знает только он
|
|
749
800
|
const columns = ["minmax(0, 1fr)"];
|
|
750
|
-
if (this.#
|
|
751
|
-
if (this.#
|
|
801
|
+
if (this.#showSize) columns.push("110px");
|
|
802
|
+
if (this.#showModified) columns.push("160px");
|
|
752
803
|
this.#grid.style.setProperty("--file_explorer_columns", columns.join(" "));
|
|
753
804
|
const visible = this.#visibleEntries();
|
|
754
805
|
for (const entry of visible) this.#body.appendChild(this.#rowNode(entry));
|
|
755
806
|
this.#grid.hidden = visible.length === 0;
|
|
756
|
-
this.#empty.hidden = visible.length > 0 || this.#
|
|
757
|
-
this.#empty.innerText = this.isFiltered() ? I18n.t("common.nothingFound") : this.#
|
|
758
|
-
this.#error.hidden = this.#
|
|
807
|
+
this.#empty.hidden = visible.length > 0 || this.#errorObject !== undefined;
|
|
808
|
+
this.#empty.innerText = this.isFiltered() ? I18n.t("common.nothingFound") : this.#emptyText;
|
|
809
|
+
this.#error.hidden = this.#errorObject === undefined;
|
|
759
810
|
this.#error.innerText = this.getErrorText();
|
|
760
|
-
this.#grid.setAttribute(
|
|
811
|
+
this.#grid.setAttribute(
|
|
812
|
+
"aria-label",
|
|
813
|
+
I18n.t("fileExplorer.gridLabel", {
|
|
814
|
+
path: this.#path === "" ? I18n.t("fileExplorer.noFolderInline") : this.#path,
|
|
815
|
+
})
|
|
816
|
+
);
|
|
761
817
|
this.#grid.setAttribute("aria-rowcount", String(visible.length + 1));
|
|
762
818
|
this.#grid.setAttribute("aria-busy", this.#loading ? "true" : "false");
|
|
763
819
|
this.#back.disabled = !this.canGoBack();
|
|
@@ -772,31 +828,35 @@ class FileExplorer {
|
|
|
772
828
|
|
|
773
829
|
#renderPath() {
|
|
774
830
|
const items = this.#pathItems();
|
|
775
|
-
this.#
|
|
776
|
-
|
|
777
|
-
|
|
831
|
+
this.#pathBar.setItems(
|
|
832
|
+
items.map((item, index) =>
|
|
833
|
+
index === items.length - 1
|
|
834
|
+
? { title: item.title }
|
|
835
|
+
: { title: item.title, clickEvent: () => this.go(item.path) }
|
|
836
|
+
)
|
|
837
|
+
);
|
|
778
838
|
return this;
|
|
779
839
|
}
|
|
780
840
|
|
|
781
841
|
/** Звенья пути: с корнем оставляем только путь от него */
|
|
782
842
|
#pathItems() {
|
|
783
843
|
const full = String(this.#path);
|
|
784
|
-
if (full === "") return [{title: I18n.t("fileExplorer.noFolder"), path: ""}];
|
|
785
|
-
const root = this.#
|
|
844
|
+
if (full === "") return [{ title: I18n.t("fileExplorer.noFolder"), path: "" }];
|
|
845
|
+
const root = this.#rootPath === undefined ? undefined : String(this.#rootPath).replace(/[\\/]+$/, "");
|
|
786
846
|
const items = [];
|
|
787
|
-
if (full.startsWith("/")) items.push({title: "/", path: "/"});
|
|
788
|
-
else if (/^[A-Za-z]:/.test(full)) items.push({title: `${full.slice(0, 2)}/`, path: `${full.slice(0, 2)}/`});
|
|
847
|
+
if (full.startsWith("/")) items.push({ title: "/", path: "/" });
|
|
848
|
+
else if (/^[A-Za-z]:/.test(full)) items.push({ title: `${full.slice(0, 2)}/`, path: `${full.slice(0, 2)}/` });
|
|
789
849
|
const base = items.length > 0 ? items[0].path : "";
|
|
790
850
|
const rest = full.slice(base.length).replace(/^[\\/]+/, "");
|
|
791
851
|
let current = base;
|
|
792
852
|
for (const part of rest.split(/[\\/]+/).filter((piece) => piece !== "")) {
|
|
793
|
-
current = current === "" ? part :
|
|
794
|
-
items.push({title: part, path: current});
|
|
853
|
+
current = current === "" ? part : current.endsWith("/") ? `${current}${part}` : `${current}/${part}`;
|
|
854
|
+
items.push({ title: part, path: current });
|
|
795
855
|
}
|
|
796
|
-
if (items.length === 0) items.push({title: full, path: full});
|
|
856
|
+
if (items.length === 0) items.push({ title: full, path: full });
|
|
797
857
|
if (root !== undefined && root !== "") {
|
|
798
858
|
const index = items.findIndex((item) => item.path === root);
|
|
799
|
-
if (index > 0) return [{title: items[index].title, path: root}].concat(items.slice(index + 1));
|
|
859
|
+
if (index > 0) return [{ title: items[index].title, path: root }].concat(items.slice(index + 1));
|
|
800
860
|
}
|
|
801
861
|
return items;
|
|
802
862
|
}
|
|
@@ -815,14 +875,14 @@ class FileExplorer {
|
|
|
815
875
|
const list = [...this.#body.querySelectorAll("file_explorer_row")];
|
|
816
876
|
if (list.length === 0) return;
|
|
817
877
|
const stop =
|
|
818
|
-
list.find((row) => row.dataset.path === this.#
|
|
819
|
-
list.find((row) => row.dataset.path === this.#
|
|
878
|
+
list.find((row) => row.dataset.path === this.#activePath) ??
|
|
879
|
+
list.find((row) => row.dataset.path === this.#focusPath) ??
|
|
820
880
|
list[0];
|
|
821
|
-
this.#
|
|
881
|
+
this.#activePath = stop.dataset.path;
|
|
822
882
|
for (const row of list) row.setAttribute("tabindex", row === stop ? "0" : "-1");
|
|
823
883
|
// Фокус возвращаем только если он был в сетке: иначе перерисовка его бы украла
|
|
824
|
-
if (this.#
|
|
825
|
-
this.#
|
|
884
|
+
if (this.#focusPath !== undefined && stop.dataset.path === this.#focusPath) {
|
|
885
|
+
this.#focusPath = undefined;
|
|
826
886
|
stop.focus();
|
|
827
887
|
}
|
|
828
888
|
return this;
|
|
@@ -830,7 +890,7 @@ class FileExplorer {
|
|
|
830
890
|
|
|
831
891
|
#focusRow(row = undefined) {
|
|
832
892
|
if (row === undefined || row === null) return this;
|
|
833
|
-
this.#
|
|
893
|
+
this.#activePath = row.dataset.path;
|
|
834
894
|
for (const other of this.#body.querySelectorAll("file_explorer_row")) {
|
|
835
895
|
other.setAttribute("tabindex", other === row ? "0" : "-1");
|
|
836
896
|
}
|
|
@@ -839,7 +899,10 @@ class FileExplorer {
|
|
|
839
899
|
}
|
|
840
900
|
|
|
841
901
|
#currentRow(target = undefined) {
|
|
842
|
-
const row =
|
|
902
|
+
const row =
|
|
903
|
+
target === undefined || target === null || typeof target.closest !== "function"
|
|
904
|
+
? null
|
|
905
|
+
: target.closest("file_explorer_row");
|
|
843
906
|
if (row !== null) return row;
|
|
844
907
|
return this.#body.querySelector('file_explorer_row[tabindex="0"]');
|
|
845
908
|
}
|
|
@@ -891,25 +954,33 @@ class FileExplorer {
|
|
|
891
954
|
|
|
892
955
|
#notifySelect() {
|
|
893
956
|
const entries = this.getSelected();
|
|
894
|
-
this.#announce(
|
|
957
|
+
this.#announce(
|
|
958
|
+
entries.length === 0
|
|
959
|
+
? I18n.t("fileExplorer.selectionCleared")
|
|
960
|
+
: I18n.t("fileExplorer.selectedCount", { count: entries.length })
|
|
961
|
+
);
|
|
895
962
|
for (const listener of [...this.#listeners]) listener(entries, this);
|
|
896
|
-
if (this.#
|
|
963
|
+
if (this.#onSelect !== undefined) this.#onSelect(entries, this);
|
|
897
964
|
return this;
|
|
898
965
|
}
|
|
899
966
|
|
|
900
967
|
/** Что видно сейчас: количество, пустота, поиск и ошибка чтения */
|
|
901
968
|
#announceState() {
|
|
902
|
-
if (this.#
|
|
969
|
+
if (this.#errorObject !== undefined) {
|
|
903
970
|
this.#status.innerText = this.getErrorText();
|
|
904
971
|
return this;
|
|
905
972
|
}
|
|
906
973
|
const visible = this.#visibleEntries().length;
|
|
907
|
-
const total = this.#entries.filter((entry) => this.#
|
|
974
|
+
const total = this.#entries.filter((entry) => this.#showHidden || entry.hidden !== true).length;
|
|
908
975
|
if (this.isFiltered()) {
|
|
909
|
-
this.#status.innerText =
|
|
976
|
+
this.#status.innerText =
|
|
977
|
+
visible === 0
|
|
978
|
+
? I18n.t("common.nothingFound")
|
|
979
|
+
: I18n.t("fileExplorer.foundCount", { found: visible, total: total });
|
|
910
980
|
return this;
|
|
911
981
|
}
|
|
912
|
-
this.#status.innerText =
|
|
982
|
+
this.#status.innerText =
|
|
983
|
+
visible === 0 ? this.#emptyText : I18n.t("fileExplorer.entriesCount", { count: visible });
|
|
913
984
|
return this;
|
|
914
985
|
}
|
|
915
986
|
|
|
@@ -922,12 +993,12 @@ class FileExplorer {
|
|
|
922
993
|
|
|
923
994
|
/** Кнопка очистки видна, только когда есть что очищать */
|
|
924
995
|
#syncClear() {
|
|
925
|
-
this.#
|
|
996
|
+
this.#searchClear.hidden = this.#filter === "";
|
|
926
997
|
return this;
|
|
927
998
|
}
|
|
928
999
|
|
|
929
1000
|
#syncLabel() {
|
|
930
|
-
if (this.#
|
|
1001
|
+
if (this.#titleText !== undefined && this.#titleText !== "") {
|
|
931
1002
|
this.#root.setAttribute("aria-labelledby", this.#title.id);
|
|
932
1003
|
this.#root.removeAttribute("aria-label");
|
|
933
1004
|
} else {
|
|
@@ -938,8 +1009,8 @@ class FileExplorer {
|
|
|
938
1009
|
}
|
|
939
1010
|
|
|
940
1011
|
#accessibleName() {
|
|
941
|
-
if (this.#
|
|
942
|
-
if (this.#
|
|
1012
|
+
if (this.#titleText !== undefined && this.#titleText !== "") return this.#titleText;
|
|
1013
|
+
if (this.#labelText !== undefined && this.#labelText !== "") return this.#labelText;
|
|
943
1014
|
return I18n.t("fileExplorer.title");
|
|
944
1015
|
}
|
|
945
1016
|
|
|
@@ -953,7 +1024,7 @@ class FileExplorer {
|
|
|
953
1024
|
// иначе ближайшая перерисовка вернула бы фокус не туда, где он был
|
|
954
1025
|
const row = event.target instanceof Element ? event.target.closest("file_explorer_row") : null;
|
|
955
1026
|
if (row === null || row.dataset.path === undefined) return;
|
|
956
|
-
this.#
|
|
1027
|
+
this.#activePath = row.dataset.path;
|
|
957
1028
|
};
|
|
958
1029
|
|
|
959
1030
|
#onClick = (event) => {
|
|
@@ -1034,4 +1105,4 @@ class FileExplorer {
|
|
|
1034
1105
|
};
|
|
1035
1106
|
}
|
|
1036
1107
|
|
|
1037
|
-
exports.FileExplorer = FileExplorer
|
|
1108
|
+
exports.FileExplorer = FileExplorer;
|