chuijs 4.0.2 → 4.0.3
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 +61 -0
- package/README.md +43 -644
- package/framework/appLayout/default/chui_app_layout.js +308 -260
- package/framework/appLayout/default/global_style.css +85 -48
- package/framework/components/chui_accordion/accordion.js +29 -26
- package/framework/components/chui_accordion/styles.css +6 -5
- package/framework/components/chui_action_sheet/action_sheet.js +59 -33
- package/framework/components/chui_action_sheet/styles.css +2 -1
- package/framework/components/chui_affix/affix.js +11 -6
- package/framework/components/chui_alert/alert.js +23 -14
- package/framework/components/chui_avatar/avatar.js +23 -22
- package/framework/components/chui_avatar_group/avatar_group.js +21 -19
- package/framework/components/chui_backtop/backtop.js +35 -19
- package/framework/components/chui_badge/badge.js +15 -13
- package/framework/components/chui_breadcrumb/breadcrumb.js +15 -13
- package/framework/components/chui_button/button.js +42 -22
- package/framework/components/chui_button/styles.css +2 -2
- package/framework/components/chui_calendar/calendar.js +12 -10
- package/framework/components/chui_card/card.js +43 -23
- package/framework/components/chui_chat/chat.js +72 -33
- package/framework/components/chui_clipboard_history/clipboard_history.js +30 -21
- package/framework/components/chui_clipboard_history/styles.css +4 -2
- package/framework/components/chui_code/code.js +2 -2
- package/framework/components/chui_code/styles.css +2 -2
- package/framework/components/chui_code_editor/code_editor.js +47 -31
- package/framework/components/chui_code_editor/styles.css +3 -2
- package/framework/components/chui_command_palette/command_palette.js +60 -37
- package/framework/components/chui_console/console.js +5 -5
- package/framework/components/chui_console/styles.css +1 -1
- package/framework/components/chui_content_block/content_block.js +33 -19
- package/framework/components/chui_content_block/styles.css +2 -2
- package/framework/components/chui_context_menu/context_menu.js +9 -4
- package/framework/components/chui_custom_element/custom_element.js +22 -15
- package/framework/components/chui_data_grid/data_grid.js +396 -234
- package/framework/components/chui_data_grid/styles.css +10 -8
- package/framework/components/chui_descriptions/descriptions.js +34 -17
- package/framework/components/chui_details/details.js +32 -26
- package/framework/components/chui_details/styles.css +5 -4
- package/framework/components/chui_diff_viewer/diff_viewer.js +91 -66
- package/framework/components/chui_diff_viewer/styles.css +1 -1
- package/framework/components/chui_divider/divider.js +13 -11
- package/framework/components/chui_drawer/drawer.js +27 -25
- package/framework/components/chui_drop_zone/drop_zone.js +31 -21
- package/framework/components/chui_empty_state/empty_state.js +20 -15
- package/framework/components/chui_fieldset/fieldset.js +37 -20
- package/framework/components/chui_fieldset/styles.css +1 -1
- package/framework/components/chui_file_explorer/file_explorer.js +157 -86
- package/framework/components/chui_file_explorer/styles.css +2 -1
- package/framework/components/chui_form/form.js +31 -19
- package/framework/components/chui_form/styles.css +1 -1
- package/framework/components/chui_gantt/gantt.js +64 -40
- package/framework/components/chui_graphs/graphs.js +301 -200
- package/framework/components/chui_graphs/styles.css +1 -1
- package/framework/components/chui_hero/hero.js +40 -22
- package/framework/components/chui_html_block/html_block.js +6 -6
- package/framework/components/chui_html_block/styles.css +2 -2
- package/framework/components/chui_hx/hx.js +3 -3
- package/framework/components/chui_hx/styles.css +11 -4
- package/framework/components/chui_icons/icons.js +2109 -2109
- package/framework/components/chui_icons/styles.css +2 -2
- package/framework/components/chui_inputs/chui_check_box/check_box.js +31 -19
- package/framework/components/chui_inputs/chui_check_box/styles.css +18 -18
- package/framework/components/chui_inputs/chui_chips/chips.js +50 -40
- package/framework/components/chui_inputs/chui_chips/styles.css +4 -2
- package/framework/components/chui_inputs/chui_color_picker/color_picker.js +36 -24
- package/framework/components/chui_inputs/chui_combo_box/combo_box.js +100 -82
- package/framework/components/chui_inputs/chui_combo_box/styles.css +14 -12
- package/framework/components/chui_inputs/chui_date/date.js +102 -66
- package/framework/components/chui_inputs/chui_date/styles.css +12 -11
- package/framework/components/chui_inputs/chui_date_range/date_range.js +40 -29
- package/framework/components/chui_inputs/chui_datetime/datetime.js +35 -27
- package/framework/components/chui_inputs/chui_email/email.js +43 -31
- package/framework/components/chui_inputs/chui_email/styles.css +5 -5
- package/framework/components/chui_inputs/chui_file/file.js +68 -41
- package/framework/components/chui_inputs/chui_file/styles.css +13 -12
- package/framework/components/chui_inputs/chui_hotkey/hotkey.js +61 -36
- package/framework/components/chui_inputs/chui_masked/masked.js +39 -28
- package/framework/components/chui_inputs/chui_mention/mention.js +77 -30
- package/framework/components/chui_inputs/chui_multi_combo_box/multi_combo_box.js +173 -153
- package/framework/components/chui_inputs/chui_multi_combo_box/styles.css +9 -7
- package/framework/components/chui_inputs/chui_number/number.js +58 -41
- package/framework/components/chui_inputs/chui_number/styles.css +7 -7
- package/framework/components/chui_inputs/chui_otp/otp.js +38 -29
- package/framework/components/chui_inputs/chui_password/password.js +58 -36
- package/framework/components/chui_inputs/chui_password/styles.css +8 -8
- package/framework/components/chui_inputs/chui_radio_button/radio_button.js +37 -25
- package/framework/components/chui_inputs/chui_radio_button/styles.css +20 -20
- package/framework/components/chui_inputs/chui_rating/rating.js +26 -22
- package/framework/components/chui_inputs/chui_select_box/select_box.js +102 -78
- package/framework/components/chui_inputs/chui_select_box/styles.css +11 -11
- package/framework/components/chui_inputs/chui_slider/slider.js +29 -26
- package/framework/components/chui_inputs/chui_slider/styles.css +2 -1
- package/framework/components/chui_inputs/chui_text/styles.css +8 -8
- package/framework/components/chui_inputs/chui_text/text.js +65 -44
- package/framework/components/chui_inputs/chui_text_area/styles.css +6 -6
- package/framework/components/chui_inputs/chui_text_area/text_area.js +51 -28
- package/framework/components/chui_inputs/chui_time/styles.css +5 -3
- package/framework/components/chui_inputs/chui_time/time.js +38 -27
- package/framework/components/chui_inputs/chui_toggle/styles.css +3 -3
- package/framework/components/chui_inputs/chui_toggle/toggle.js +16 -16
- package/framework/components/chui_json_editor/json_editor.js +53 -43
- package/framework/components/chui_json_viewer/json_viewer.js +52 -33
- package/framework/components/chui_json_viewer/styles.css +3 -2
- package/framework/components/chui_kanban/kanban.js +52 -34
- package/framework/components/chui_kbd/kbd.js +12 -10
- package/framework/components/chui_label/label.js +14 -12
- package/framework/components/chui_label/styles.css +2 -2
- package/framework/components/chui_link/link.js +27 -20
- package/framework/components/chui_list/list.js +31 -27
- package/framework/components/chui_list/styles.css +2 -1
- package/framework/components/chui_markdown_editor/markdown_editor.js +60 -44
- package/framework/components/chui_media/audio.js +326 -227
- package/framework/components/chui_media/audio_styles.css +40 -21
- package/framework/components/chui_media/image.js +70 -38
- package/framework/components/chui_media/image_compare.js +52 -21
- package/framework/components/chui_media/image_styles.css +3 -3
- package/framework/components/chui_media/video.js +155 -62
- package/framework/components/chui_media/video_styles.css +33 -16
- package/framework/components/chui_menu_bar/menu_bar.js +26 -24
- package/framework/components/chui_menu_bar/styles.css +4 -2
- package/framework/components/chui_modal/modal.js +44 -43
- package/framework/components/chui_modal/styles.css +3 -2
- package/framework/components/chui_notification/notification.js +50 -34
- package/framework/components/chui_notification/notification_download.js +31 -25
- package/framework/components/chui_notification/notification_download_progress.css +3 -2
- package/framework/components/chui_notification/notification_download_progress.js +47 -41
- package/framework/components/chui_notification/notification_update.js +23 -16
- package/framework/components/chui_notification/status_plate.js +2 -2
- package/framework/components/chui_notification/styles_download.css +1 -1
- package/framework/components/chui_notification/styles_update.css +1 -1
- package/framework/components/chui_org_chart/org_chart.js +74 -35
- package/framework/components/chui_pagination/pagination.js +62 -27
- package/framework/components/chui_paragraph/paragraph.js +2 -2
- package/framework/components/chui_paragraph/styles.css +1 -1
- package/framework/components/chui_popover/popover.js +49 -28
- package/framework/components/chui_popups/popups.js +137 -108
- package/framework/components/chui_popups/styles.css +2 -2
- package/framework/components/chui_print_button/print_button.js +33 -23
- package/framework/components/chui_progress_bar/progress_bar.js +23 -15
- package/framework/components/chui_progress_bar/styles.css +2 -2
- package/framework/components/chui_progress_ring/progress_ring.js +17 -15
- package/framework/components/chui_qrcode/qrcode.js +75 -31
- package/framework/components/chui_query_builder/query_builder.js +216 -123
- package/framework/components/chui_radio_group/radio_group.js +36 -26
- package/framework/components/chui_radio_group/styles.css +1 -1
- package/framework/components/chui_result/result.js +26 -16
- package/framework/components/chui_scheduler/scheduler.js +57 -35
- package/framework/components/chui_scheduler/styles.css +6 -2
- package/framework/components/chui_segmented_control/segmented_control.js +27 -21
- package/framework/components/chui_settings/settings.js +122 -48
- package/framework/components/chui_shortcut_help/shortcut_help.js +55 -32
- package/framework/components/chui_sidebar_nav/sidebar_nav.js +33 -23
- package/framework/components/chui_skeleton/skeleton.js +29 -24
- package/framework/components/chui_slideshow/slideshow.js +60 -41
- package/framework/components/chui_slideshow/styles.css +18 -12
- package/framework/components/chui_spinner/spinner.js +8 -8
- package/framework/components/chui_spinner/styles.css +0 -1
- package/framework/components/chui_split_button/split_button.js +23 -18
- package/framework/components/chui_split_button/styles.css +1 -1
- package/framework/components/chui_splitter/splitter.js +35 -28
- package/framework/components/chui_statistic/statistic.js +65 -38
- package/framework/components/chui_status_bar/status_bar.js +11 -9
- package/framework/components/chui_stepper/stepper.js +27 -22
- package/framework/components/chui_sticky_bar/sticky_bar.js +9 -4
- package/framework/components/chui_sticky_bar/styles.css +3 -1
- package/framework/components/chui_tabs/chui_tabs.js +28 -22
- package/framework/components/chui_tabs/styles.css +3 -3
- package/framework/components/chui_text_editor/styles.css +20 -19
- package/framework/components/chui_text_editor/text_editor.js +85 -61
- package/framework/components/chui_text_editor/text_editor_panel.js +346 -284
- package/framework/components/chui_theme_toggle/theme_toggle.js +27 -22
- package/framework/components/chui_timeline/timeline.js +14 -12
- package/framework/components/chui_toast_queue/toast_queue.js +52 -46
- package/framework/components/chui_toolbar/toolbar.js +20 -13
- package/framework/components/chui_tooltip/tooltip.js +37 -24
- package/framework/components/chui_tour/styles.css +8 -3
- package/framework/components/chui_tour/tour.js +61 -27
- package/framework/components/chui_transfer/styles.css +2 -1
- package/framework/components/chui_transfer/transfer.js +75 -42
- package/framework/components/chui_tree_table/tree_table.js +43 -29
- package/framework/components/chui_tree_view/styles.css +4 -2
- package/framework/components/chui_tree_view/tree_view.js +142 -99
- package/framework/components/chui_watermark/watermark.js +24 -16
- package/framework/components/chui_webview/styles.css +2 -2
- package/framework/components/chui_webview/webview.js +62 -50
- package/framework/components/telegram_bot/chui_telegram_bot.js +71 -64
- package/framework/modules/chui_a11y/chui_a11y.js +77 -46
- package/framework/modules/chui_animations/animations.js +42 -42
- package/framework/modules/chui_animations/styles.css +1 -1
- package/framework/modules/chui_bridge/preload.js +36 -25
- package/framework/modules/chui_functions.js +52 -50
- package/framework/modules/chui_highlight/chui_highlight.js +25 -22
- package/framework/modules/chui_i18n/chui_i18n.js +9 -7
- 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 +3 -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 +8 -8
- package/framework/modules/chui_i18n/locales/validate.js +3 -3
- package/framework/modules/chui_json/chui_json.js +9 -9
- package/framework/modules/chui_layout/chui_layout.js +12 -10
- package/framework/modules/chui_logger/chui_logger.js +29 -25
- package/framework/modules/chui_page/page.js +33 -27
- package/framework/modules/chui_qr/chui_qr.js +158 -67
- package/framework/modules/chui_scrollbar/chui_scrollbar.js +35 -24
- package/framework/modules/chui_scrollbar/styles.css +3 -1
- package/framework/modules/chui_services/chui_channels.js +27 -13
- package/framework/modules/chui_services/chui_services.js +39 -31
- package/framework/modules/chui_services/desktop.js +32 -31
- package/framework/modules/chui_sortable/chui_sortable.js +17 -12
- package/framework/modules/chui_validate/chui_validate.js +7 -4
- package/framework/tray_bar/chui_menu_items.js +136 -46
- package/index.d.ts +4814 -0
- package/index.js +385 -292
- package/package.json +79 -68
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
const {Animation} = require(
|
|
2
|
-
const {Icon, Icons} = require(
|
|
3
|
-
const {Button} = require("../../components/chui_button/button");
|
|
1
|
+
const { Animation } = require("../../modules/chui_animations/animations");
|
|
2
|
+
const { Icon, Icons } = require("../../components/chui_icons/icons");
|
|
3
|
+
const { Button } = require("../../components/chui_button/button");
|
|
4
4
|
// НАСТРОЙКИ
|
|
5
|
-
const {Dialog} = require("../../components/chui_modal/modal");
|
|
6
|
-
const {Label} = require("../../components/chui_label/label");
|
|
7
|
-
const {ContentBlock} = require("../../components/chui_content_block/content_block");
|
|
8
|
-
const {Popup} = require("../../components/chui_popups/popups");
|
|
9
|
-
const {UpdateNotification} = require("../../components/chui_notification/notification_update");
|
|
10
|
-
const {ipcRenderer} = require("electron");
|
|
11
|
-
const chui_functions = require(
|
|
12
|
-
const {DownloadNotification} = require("../../components/chui_notification/notification_download");
|
|
13
|
-
const {Log} = require("../../../framework/modules/chui_logger/chui_logger");
|
|
14
|
-
const {MenuKeyboard, makeKeyboardButton} = require(
|
|
15
|
-
const {LAYOUT_ROLE, LayoutRefs, registerLayout, requireLayoutNode} = require(
|
|
16
|
-
const {BRIDGE_CHANNELS} = require(
|
|
17
|
-
const {OverlayScrollbars} = require(
|
|
18
|
-
const {MenuBar} = require(
|
|
19
|
-
const {Avatar} = require(
|
|
20
|
-
const {EmptyState} = require(
|
|
21
|
-
const {I18n} = require(
|
|
5
|
+
const { Dialog } = require("../../components/chui_modal/modal");
|
|
6
|
+
const { Label } = require("../../components/chui_label/label");
|
|
7
|
+
const { ContentBlock } = require("../../components/chui_content_block/content_block");
|
|
8
|
+
const { Popup } = require("../../components/chui_popups/popups");
|
|
9
|
+
const { UpdateNotification } = require("../../components/chui_notification/notification_update");
|
|
10
|
+
const { ipcRenderer } = require("electron");
|
|
11
|
+
const chui_functions = require("../../modules/chui_functions");
|
|
12
|
+
const { DownloadNotification } = require("../../components/chui_notification/notification_download");
|
|
13
|
+
const { Log } = require("../../../framework/modules/chui_logger/chui_logger");
|
|
14
|
+
const { MenuKeyboard, makeKeyboardButton } = require("../../modules/chui_a11y/chui_a11y");
|
|
15
|
+
const { LAYOUT_ROLE, LayoutRefs, registerLayout, requireLayoutNode } = require("../../modules/chui_layout/chui_layout");
|
|
16
|
+
const { BRIDGE_CHANNELS } = require("../../modules/chui_services/chui_channels");
|
|
17
|
+
const { OverlayScrollbars } = require("../../modules/chui_scrollbar/chui_scrollbar");
|
|
18
|
+
const { MenuBar } = require("../../components/chui_menu_bar/menu_bar");
|
|
19
|
+
const { Avatar } = require("../../components/chui_avatar/avatar");
|
|
20
|
+
const { EmptyState } = require("../../components/chui_empty_state/empty_state");
|
|
21
|
+
const { I18n } = require("../../modules/chui_i18n/chui_i18n");
|
|
22
22
|
const fs = require("fs");
|
|
23
23
|
|
|
24
24
|
// Глобальные обработчики ошибок renderer-процесса вешаются один раз на процесс
|
|
@@ -28,15 +28,15 @@ class Events {
|
|
|
28
28
|
#route_event = undefined;
|
|
29
29
|
constructor() {}
|
|
30
30
|
route(page) {
|
|
31
|
-
this.#route_event = new CustomEvent("route_event_"+page.constructor.name, {
|
|
31
|
+
this.#route_event = new CustomEvent("route_event_" + page.constructor.name, {
|
|
32
32
|
bubbles: true,
|
|
33
33
|
detail: {
|
|
34
34
|
class: page.constructor.name,
|
|
35
35
|
title: page.getTitle(),
|
|
36
|
-
page: page
|
|
36
|
+
page: page,
|
|
37
37
|
},
|
|
38
38
|
});
|
|
39
|
-
document.dispatchEvent(this.#route_event)
|
|
39
|
+
document.dispatchEvent(this.#route_event);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -88,21 +88,21 @@ class Route extends Events {
|
|
|
88
88
|
let center = requireLayoutNode(LAYOUT_ROLE.CENTER);
|
|
89
89
|
//
|
|
90
90
|
setPageName(page.getTitle());
|
|
91
|
-
center.innerHTML =
|
|
91
|
+
center.innerHTML = "";
|
|
92
92
|
// Содержимое собирает сама страница (`Page#render`), оболочка в него не подмешивает
|
|
93
93
|
// ничего: узел `page_title` (был здесь) дублировал заголовок, который страница
|
|
94
94
|
// рисует сама, а `Page#render` отдаёт один и тот же узел — повторный переход на тот
|
|
95
95
|
// же экземпляр страницы (кнопка «Назад» из `Page#setBackButton`) добавлял второй
|
|
96
96
|
// заголовок. Место названия — шапка (`setPageName`).
|
|
97
97
|
center.appendChild(page.render());
|
|
98
|
-
const _page = center.querySelector(
|
|
98
|
+
const _page = center.querySelector("page");
|
|
99
99
|
new Animation(_page).fadeIn();
|
|
100
100
|
// Стирания `center.removeAttribute("style")` здесь больше нет: инлайновых
|
|
101
101
|
// стилей на `center` не ставит ни одна часть оболочки, а стирать чужое
|
|
102
102
|
// (если появится) — это ловушка, на которой уже терялись `display: flex`
|
|
103
103
|
applyPageMenuBar(requireLayoutNode(LAYOUT_ROLE.HEADER), page.getMenuBar());
|
|
104
104
|
|
|
105
|
-
this.route(page)
|
|
105
|
+
this.route(page);
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -161,18 +161,21 @@ function makeHeaderButtonBox(options = { title: undefined, icon: undefined, reve
|
|
|
161
161
|
* подобраны под вид набора; цвет наследуется от кнопки через `currentColor`.
|
|
162
162
|
*/
|
|
163
163
|
const WINDOW_ICONS = {
|
|
164
|
-
minimize:
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
minimize:
|
|
165
|
+
'<svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><path d="M4.5 7.5 L10 12.5 L15.5 7.5" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
166
|
+
maximize:
|
|
167
|
+
'<svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><path d="M4.5 12.5 L10 7.5 L15.5 12.5" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
168
|
+
restore:
|
|
169
|
+
'<svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><path d="M10 5 L15 10 L10 15 L5 10 Z" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
|
167
170
|
close: '<svg viewBox="0 0 20 20" aria-hidden="true" focusable="false"><path d="M5 5 L15 15 M15 5 L5 15" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>',
|
|
168
171
|
};
|
|
169
172
|
|
|
170
173
|
class WindowControls {
|
|
171
174
|
// Управление кнопками
|
|
172
|
-
#box = document.createElement("wc_box")
|
|
173
|
-
#close = document.createElement("wc_close")
|
|
174
|
-
#maximize = document.createElement("wc_maximize")
|
|
175
|
-
#minimize = document.createElement("wc_minimize")
|
|
175
|
+
#box = document.createElement("wc_box");
|
|
176
|
+
#close = document.createElement("wc_close");
|
|
177
|
+
#maximize = document.createElement("wc_maximize");
|
|
178
|
+
#minimize = document.createElement("wc_minimize");
|
|
176
179
|
// Подписка на нересайзящееся окно: set() вызывается и из конструктора Header, и из
|
|
177
180
|
// addWC(), поэтому в set() слушатель ipcRenderer накапливался.
|
|
178
181
|
#resizable_false_event = () => this.#maximize.remove();
|
|
@@ -188,15 +191,15 @@ class WindowControls {
|
|
|
188
191
|
this.#setButton(this.#minimize, I18n.t("appLayout.window.minimize"), "minimize");
|
|
189
192
|
// «Развернуть» — шеврон вверх, зеркало «свернуть»: пара читается как одна ось
|
|
190
193
|
this.#setButton(this.#maximize, I18n.t("appLayout.window.maximize"), "maximize");
|
|
191
|
-
this.#close.addEventListener("click", () => this.#windowAction("close"))
|
|
192
|
-
this.#minimize.addEventListener("click", () => this.#windowAction("minimize"))
|
|
194
|
+
this.#close.addEventListener("click", () => this.#windowAction("close"));
|
|
195
|
+
this.#minimize.addEventListener("click", () => this.#windowAction("minimize"));
|
|
193
196
|
// Разворот и восстановление — одно действие: решение принимает окно, а не кнопка
|
|
194
|
-
this.#maximize.addEventListener("click", () => this.#windowAction("toggle-maximize"))
|
|
197
|
+
this.#maximize.addEventListener("click", () => this.#windowAction("toggle-maximize"));
|
|
195
198
|
// Разворот приходит не только от нашей кнопки (двойной клик по полосе, меню окна),
|
|
196
199
|
// поэтому состояние читаем по факту изменения размера окна
|
|
197
|
-
window.addEventListener("resize", this.#sync_maximized_event)
|
|
198
|
-
this.#syncMaximized()
|
|
199
|
-
ipcRenderer.on("chui_resizable_false", this.#resizable_false_event)
|
|
200
|
+
window.addEventListener("resize", this.#sync_maximized_event);
|
|
201
|
+
this.#syncMaximized();
|
|
202
|
+
ipcRenderer.on("chui_resizable_false", this.#resizable_false_event);
|
|
200
203
|
}
|
|
201
204
|
/** Кнопка окна — значок с именем для скринридера, фокусом и активацией с клавиатуры.
|
|
202
205
|
* Значок рисуется линиями (`WINDOW_ICONS`), размер задаёт CSS (см. global_style.css) */
|
|
@@ -247,13 +250,13 @@ class WindowControls {
|
|
|
247
250
|
#sync_maximized_event = () => this.#syncMaximized();
|
|
248
251
|
set(pos_bool = Boolean()) {
|
|
249
252
|
if (pos_bool) {
|
|
250
|
-
this.#box.appendChild(this.#close)
|
|
251
|
-
this.#box.appendChild(this.#maximize)
|
|
252
|
-
this.#box.appendChild(this.#minimize)
|
|
253
|
+
this.#box.appendChild(this.#close);
|
|
254
|
+
this.#box.appendChild(this.#maximize);
|
|
255
|
+
this.#box.appendChild(this.#minimize);
|
|
253
256
|
} else {
|
|
254
|
-
this.#box.appendChild(this.#minimize)
|
|
255
|
-
this.#box.appendChild(this.#maximize)
|
|
256
|
-
this.#box.appendChild(this.#close)
|
|
257
|
+
this.#box.appendChild(this.#minimize);
|
|
258
|
+
this.#box.appendChild(this.#maximize);
|
|
259
|
+
this.#box.appendChild(this.#close);
|
|
257
260
|
}
|
|
258
261
|
return this.#box;
|
|
259
262
|
}
|
|
@@ -276,7 +279,7 @@ class Header {
|
|
|
276
279
|
this.#header_main.appendChild(this.#header_left_box);
|
|
277
280
|
this.#header_main.appendChild(this.#header_right_box);
|
|
278
281
|
this.#header_main.appendChild(this.#wc_box.set(false));
|
|
279
|
-
this.#header.appendChild(this.#header_main)
|
|
282
|
+
this.#header.appendChild(this.#header_main);
|
|
280
283
|
this.#observeHeight();
|
|
281
284
|
}
|
|
282
285
|
/*
|
|
@@ -316,7 +319,8 @@ class Header {
|
|
|
316
319
|
this.#header_main.insertBefore(this.#wc_box.set(boolean), this.#header_main.firstChild);
|
|
317
320
|
}
|
|
318
321
|
addToLeftBeforeTitle(...components) {
|
|
319
|
-
for (let component of components)
|
|
322
|
+
for (let component of components)
|
|
323
|
+
this.#header_left_box.insertBefore(component, this.#header_left_box.firstChild);
|
|
320
324
|
}
|
|
321
325
|
/** Прежнее имя с опечаткой (`Tittle`): оставлено синонимом, чтобы не ломать чужие вызовы */
|
|
322
326
|
addToLeftBeforeTittle(...components) {
|
|
@@ -324,11 +328,12 @@ class Header {
|
|
|
324
328
|
}
|
|
325
329
|
addToLeft(...components) {
|
|
326
330
|
for (let component of components) {
|
|
327
|
-
this.#header_left_box.appendChild(component)
|
|
331
|
+
this.#header_left_box.appendChild(component);
|
|
328
332
|
}
|
|
329
333
|
}
|
|
330
334
|
addToRight(...components) {
|
|
331
|
-
for (let component of components)
|
|
335
|
+
for (let component of components)
|
|
336
|
+
this.#header_right_box.insertBefore(component, this.#header_right_box.firstChild);
|
|
332
337
|
}
|
|
333
338
|
removeToRight(...components) {
|
|
334
339
|
for (let component of components) {
|
|
@@ -344,7 +349,7 @@ class Header {
|
|
|
344
349
|
}
|
|
345
350
|
|
|
346
351
|
class Center {
|
|
347
|
-
#center = document.createElement(
|
|
352
|
+
#center = document.createElement("center");
|
|
348
353
|
constructor(layout) {
|
|
349
354
|
this.#center.id = "center";
|
|
350
355
|
layout.set(LAYOUT_ROLE.CENTER, this.#center);
|
|
@@ -356,12 +361,14 @@ class Center {
|
|
|
356
361
|
|
|
357
362
|
class AppMenu {
|
|
358
363
|
#routeList = [];
|
|
359
|
-
#appMenu = document.createElement(
|
|
364
|
+
#appMenu = document.createElement("app_menu");
|
|
360
365
|
#appMenuWidth = 400;
|
|
361
366
|
#appMenuOffset = 25;
|
|
362
|
-
get #appMenuSlide() {
|
|
363
|
-
|
|
364
|
-
|
|
367
|
+
get #appMenuSlide() {
|
|
368
|
+
return this.#appMenuWidth + this.#appMenuOffset;
|
|
369
|
+
}
|
|
370
|
+
#routeViews = document.createElement("route_views");
|
|
371
|
+
#appMenuButton = document.createElement("app_menu_button");
|
|
365
372
|
// Шапка панели: плитка со значком и заголовок — панель читается выдвижным ящиком
|
|
366
373
|
#app_menu_header = document.createElement("app_menu_header");
|
|
367
374
|
#app_menu_header_icon = document.createElement("app_menu_header_icon");
|
|
@@ -379,7 +386,7 @@ class AppMenu {
|
|
|
379
386
|
#routeKeyboard = undefined;
|
|
380
387
|
#header = undefined;
|
|
381
388
|
#center = undefined;
|
|
382
|
-
#page_name = document.createElement(
|
|
389
|
+
#page_name = document.createElement("page_name");
|
|
383
390
|
constructor(header, center, layout) {
|
|
384
391
|
this.#header = header;
|
|
385
392
|
this.#center = center;
|
|
@@ -389,9 +396,9 @@ class AppMenu {
|
|
|
389
396
|
this.#appMenu.style.width = `${this.#appMenuWidth}px`;
|
|
390
397
|
this.#appMenu.style.left = `calc(-${this.#appMenuSlide}px)`;
|
|
391
398
|
//
|
|
392
|
-
this.#appMenuButton.id = "appMenuButton"
|
|
399
|
+
this.#appMenuButton.id = "appMenuButton";
|
|
393
400
|
// Список пунктов — тот узел, которым управляет кнопка: `aria-controls` ссылается на него
|
|
394
|
-
this.#routeViews.id = "chui_app_menu"
|
|
401
|
+
this.#routeViews.id = "chui_app_menu";
|
|
395
402
|
// Значок — украшение: без подписи имя кнопки складывалось из лигатуры («menu»)
|
|
396
403
|
this.#appMenuButton.setAttribute("aria-label", I18n.t("appLayout.menu"));
|
|
397
404
|
this.#appMenuButton.setAttribute("title", I18n.t("appLayout.menu"));
|
|
@@ -399,16 +406,16 @@ class AppMenu {
|
|
|
399
406
|
this.#appMenuButton.addEventListener("click", () => {
|
|
400
407
|
if (this.#appMenuButton.classList.contains("app_menu_button_active")) this.#close();
|
|
401
408
|
else this.#open();
|
|
402
|
-
})
|
|
403
|
-
header.set().addEventListener(
|
|
409
|
+
});
|
|
410
|
+
header.set().addEventListener("click", (e) => {
|
|
404
411
|
if (e.target !== this.#appMenuButton) this.#close();
|
|
405
|
-
})
|
|
406
|
-
center.addEventListener(
|
|
412
|
+
});
|
|
413
|
+
center.addEventListener("click", (e) => {
|
|
407
414
|
if (!this.#appMenu.contains(e.target)) {
|
|
408
415
|
if (this.#appMenu.style.transform === `translateX(${this.#appMenuSlide}px)`) this.#close();
|
|
409
416
|
}
|
|
410
|
-
})
|
|
411
|
-
this.#page_name.id = "page_name"
|
|
417
|
+
});
|
|
418
|
+
this.#page_name.id = "page_name";
|
|
412
419
|
layout.set(LAYOUT_ROLE.PAGE_NAME, this.#page_name);
|
|
413
420
|
header.addToLeft(this.#appMenuButton, this.#page_name);
|
|
414
421
|
this.#app_menu_header_icon.appendChild(new Icon(Icons.NAVIGATION.MENU_OPEN, "16px").set());
|
|
@@ -418,18 +425,18 @@ class AppMenu {
|
|
|
418
425
|
// Список пунктов назван головкой панели: скринридер читает «Меню», а не безымянный список
|
|
419
426
|
this.#app_menu_header_title.id = "chui_app_menu_title";
|
|
420
427
|
this.#routeViews.setAttribute("aria-labelledby", this.#app_menu_header_title.id);
|
|
421
|
-
this.#app_menu_header.appendChild(this.#app_menu_header_icon)
|
|
422
|
-
this.#app_menu_header.appendChild(this.#app_menu_header_title)
|
|
428
|
+
this.#app_menu_header.appendChild(this.#app_menu_header_icon);
|
|
429
|
+
this.#app_menu_header.appendChild(this.#app_menu_header_title);
|
|
423
430
|
this.#app_menu_empty.innerText = I18n.t("appLayout.search.empty");
|
|
424
431
|
// Подсказка лежит внутри списка, но пунктом не является: клавиатура меню её не видит (itemSelector "route")
|
|
425
|
-
this.#routeViews.appendChild(this.#app_menu_empty)
|
|
426
|
-
this.#appMenu.appendChild(this.#app_menu_header)
|
|
427
|
-
this.#appMenu.appendChild(this.#routeViews)
|
|
432
|
+
this.#routeViews.appendChild(this.#app_menu_empty);
|
|
433
|
+
this.#appMenu.appendChild(this.#app_menu_header);
|
|
434
|
+
this.#appMenu.appendChild(this.#routeViews);
|
|
428
435
|
// Клавиатура и aria меню маршрутов: стрелки по пунктам, Escape закрывает и возвращает фокус на кнопку
|
|
429
436
|
this.#routeKeyboard = new MenuKeyboard(this.#routeViews, {
|
|
430
437
|
itemSelector: "route",
|
|
431
438
|
onEscape: () => this.#close(),
|
|
432
|
-
trigger: this.#appMenuButton
|
|
439
|
+
trigger: this.#appMenuButton,
|
|
433
440
|
});
|
|
434
441
|
makeKeyboardButton(this.#appMenuButton);
|
|
435
442
|
// Закрытая панель объявляется закрытой сразу: иначе у кнопки не было бы aria-expanded,
|
|
@@ -466,18 +473,18 @@ class AppMenu {
|
|
|
466
473
|
enableSearchInput() {
|
|
467
474
|
if (this.#search_enabled) return;
|
|
468
475
|
this.#search_enabled = true;
|
|
469
|
-
this.#app_menu_search_input.classList.add("app_menu_search_input")
|
|
470
|
-
this.#app_menu_search_input.placeholder = I18n.t("appLayout.search.placeholder")
|
|
471
|
-
this.#app_menu_search_input.setAttribute("aria-label", I18n.t("appLayout.search.sections"))
|
|
476
|
+
this.#app_menu_search_input.classList.add("app_menu_search_input");
|
|
477
|
+
this.#app_menu_search_input.placeholder = I18n.t("appLayout.search.placeholder");
|
|
478
|
+
this.#app_menu_search_input.setAttribute("aria-label", I18n.t("appLayout.search.sections"));
|
|
472
479
|
// Подсветку поля даёт :focus-within на поверхности поиска — инлайновых стилей больше нет
|
|
473
480
|
this.#app_menu_search_input.addEventListener("input", (event) => {
|
|
474
|
-
this.#filterRoutes(event.target.value)
|
|
475
|
-
})
|
|
481
|
+
this.#filterRoutes(event.target.value);
|
|
482
|
+
});
|
|
476
483
|
this.#app_menu_search_icon.innerHTML = new Icon(Icons.ACTIONS.SEARCH, "15px").getHTML();
|
|
477
|
-
this.#app_menu_search_box.appendChild(this.#app_menu_search_icon)
|
|
478
|
-
this.#app_menu_search_box.appendChild(this.#app_menu_search_input)
|
|
479
|
-
this.#app_menu_search_main.appendChild(this.#app_menu_search_box)
|
|
480
|
-
this.#appMenu.insertBefore(this.#app_menu_search_main, this.#routeViews)
|
|
484
|
+
this.#app_menu_search_box.appendChild(this.#app_menu_search_icon);
|
|
485
|
+
this.#app_menu_search_box.appendChild(this.#app_menu_search_input);
|
|
486
|
+
this.#app_menu_search_main.appendChild(this.#app_menu_search_box);
|
|
487
|
+
this.#appMenu.insertBefore(this.#app_menu_search_main, this.#routeViews);
|
|
481
488
|
}
|
|
482
489
|
// Пункты списка: подсказка о пустом результате лежит там же, поэтому фильтруем по тегу
|
|
483
490
|
#routes() {
|
|
@@ -499,9 +506,9 @@ class AppMenu {
|
|
|
499
506
|
let center = this.#center;
|
|
500
507
|
//
|
|
501
508
|
setPageName(page.getTitle());
|
|
502
|
-
center.innerHTML =
|
|
509
|
+
center.innerHTML = "";
|
|
503
510
|
center.appendChild(page.render());
|
|
504
|
-
const _page = center.querySelector(
|
|
511
|
+
const _page = center.querySelector("page");
|
|
505
512
|
new Animation(_page).fadeIn();
|
|
506
513
|
// Стирания `center.removeAttribute("style")` здесь больше нет: инлайновых
|
|
507
514
|
// стилей на `center` не ставит ни одна часть оболочки, а стирать чужое
|
|
@@ -510,30 +517,30 @@ class AppMenu {
|
|
|
510
517
|
}
|
|
511
518
|
setRouteTest(page) {
|
|
512
519
|
this.#routeList.push(page);
|
|
513
|
-
let test = this.#routeList.filter(route => route.getTitle().includes(page.getTitle()));
|
|
520
|
+
let test = this.#routeList.filter((route) => route.getTitle().includes(page.getTitle()));
|
|
514
521
|
if (test.length === 1) {
|
|
515
|
-
let button_route = document.createElement(
|
|
516
|
-
let icon_menu = document.createElement(
|
|
522
|
+
let button_route = document.createElement("route");
|
|
523
|
+
let icon_menu = document.createElement("route_icon");
|
|
517
524
|
icon_menu.innerHTML = page.getIcon();
|
|
518
|
-
let title_menu = document.createElement(
|
|
525
|
+
let title_menu = document.createElement("route_title");
|
|
519
526
|
title_menu.innerHTML = page.getTitle();
|
|
520
527
|
if (page.getMain()) {
|
|
521
528
|
this.go(page);
|
|
522
529
|
button_route.classList.add("route_active");
|
|
523
530
|
}
|
|
524
|
-
button_route.addEventListener(
|
|
525
|
-
const eventAwesome = new CustomEvent("route_event_"+page.constructor.name, {
|
|
531
|
+
button_route.addEventListener("click", () => {
|
|
532
|
+
const eventAwesome = new CustomEvent("route_event_" + page.constructor.name, {
|
|
526
533
|
bubbles: true,
|
|
527
534
|
detail: {
|
|
528
535
|
class: page.constructor.name,
|
|
529
536
|
title: page.getTitle(),
|
|
530
|
-
page: page
|
|
537
|
+
page: page,
|
|
531
538
|
},
|
|
532
539
|
});
|
|
533
|
-
document.dispatchEvent(eventAwesome)
|
|
540
|
+
document.dispatchEvent(eventAwesome);
|
|
534
541
|
|
|
535
|
-
if (!button_route.classList.contains(
|
|
536
|
-
for (const act of this.#routes()) act.classList.remove(
|
|
542
|
+
if (!button_route.classList.contains("route_active")) {
|
|
543
|
+
for (const act of this.#routes()) act.classList.remove("route_active");
|
|
537
544
|
this.go(page);
|
|
538
545
|
button_route.classList.add("route_active");
|
|
539
546
|
if (this.#auto_close) {
|
|
@@ -544,15 +551,15 @@ class AppMenu {
|
|
|
544
551
|
}
|
|
545
552
|
});
|
|
546
553
|
if (page.getIcon() !== undefined) {
|
|
547
|
-
button_route.appendChild(icon_menu)
|
|
554
|
+
button_route.appendChild(icon_menu);
|
|
548
555
|
}
|
|
549
|
-
button_route.appendChild(title_menu)
|
|
550
|
-
this.#routeViews.appendChild(button_route)
|
|
556
|
+
button_route.appendChild(title_menu);
|
|
557
|
+
this.#routeViews.appendChild(button_route);
|
|
551
558
|
this.#routeKeyboard.refresh();
|
|
552
559
|
// Новый пункт мог не пройти уже введённый фильтр — список и подсказка пересобираются
|
|
553
560
|
this.#filterRoutes(this.#app_menu_search_input.value);
|
|
554
561
|
} else {
|
|
555
|
-
Log.error(I18n.t("appLayout.search.alreadyAdded", {title: page.getTitle()}))
|
|
562
|
+
Log.error(I18n.t("appLayout.search.alreadyAdded", { title: page.getTitle() }));
|
|
556
563
|
}
|
|
557
564
|
}
|
|
558
565
|
setAutoClose() {
|
|
@@ -564,7 +571,7 @@ class AppMenu {
|
|
|
564
571
|
}
|
|
565
572
|
|
|
566
573
|
class NotificationBox {
|
|
567
|
-
#notification_box = document.createElement(
|
|
574
|
+
#notification_box = document.createElement("notification_box");
|
|
568
575
|
#notification_box_main = document.createElement("notification_box_main");
|
|
569
576
|
// Шапка ящика: заголовок со счётчиком истории и действие «Очистить»
|
|
570
577
|
#notification_box_header = document.createElement("notification_box_header");
|
|
@@ -578,16 +585,16 @@ class NotificationBox {
|
|
|
578
585
|
#observer = undefined;
|
|
579
586
|
#notification_box_width = 400;
|
|
580
587
|
#notification_box_width_test = 425;
|
|
581
|
-
#notification_button = document.createElement(
|
|
588
|
+
#notification_button = document.createElement("notification_button");
|
|
582
589
|
constructor(header, center, layout) {
|
|
583
590
|
layout.set(LAYOUT_ROLE.NOTIFICATION_BOX, this.#notification_box);
|
|
584
591
|
layout.set(LAYOUT_ROLE.NOTIFICATION_BOX_MAIN, this.#notification_box_main);
|
|
585
|
-
this.#notification_box_main.id =
|
|
592
|
+
this.#notification_box_main.id = "chui_notification_box";
|
|
586
593
|
// Шапка: заголовок, счётчик записей и очистка списка
|
|
587
594
|
this.#notification_box_title.innerText = I18n.t("appLayout.notifications.title");
|
|
588
595
|
this.#notification_box_count.setAttribute("aria-hidden", "true");
|
|
589
|
-
this.#notification_box_header_text.appendChild(this.#notification_box_title)
|
|
590
|
-
this.#notification_box_header_text.appendChild(this.#notification_box_count)
|
|
596
|
+
this.#notification_box_header_text.appendChild(this.#notification_box_title);
|
|
597
|
+
this.#notification_box_header_text.appendChild(this.#notification_box_count);
|
|
591
598
|
// Очистка — обычная кнопка-действие, а не Button: в шапке нужна тихая ссылка-действие
|
|
592
599
|
this.#clear_button.className = "notification_box_clear";
|
|
593
600
|
this.#clear_button.type = "button";
|
|
@@ -596,22 +603,22 @@ class NotificationBox {
|
|
|
596
603
|
this.#clear_button.appendChild(new Icon(Icons.ACTIONS.DELETE, "14px").set());
|
|
597
604
|
const clear_title = document.createElement("span");
|
|
598
605
|
clear_title.innerText = I18n.t("appLayout.notifications.clear");
|
|
599
|
-
this.#clear_button.appendChild(clear_title)
|
|
606
|
+
this.#clear_button.appendChild(clear_title);
|
|
600
607
|
this.#clear_button.addEventListener("click", () => this.#clearAll());
|
|
601
|
-
this.#notification_box_header.appendChild(this.#notification_box_header_text)
|
|
602
|
-
this.#notification_box_header.appendChild(this.#clear_button)
|
|
608
|
+
this.#notification_box_header.appendChild(this.#notification_box_header_text);
|
|
609
|
+
this.#notification_box_header.appendChild(this.#clear_button);
|
|
603
610
|
// Пустое состояние — общий компонент, а не своя разметка из значка и подписи: это был
|
|
604
611
|
// третий вариант одного и того же блока (здесь, в поиске меню, в таблицах). Заодно он
|
|
605
612
|
// объявляется скринридеру (role="status"), а прежняя заглушка молчала
|
|
606
613
|
this.#empty_state = new EmptyState({
|
|
607
614
|
icon: Icons.SOCIAL.NOTIFICATIONS_OFF,
|
|
608
615
|
title: I18n.t("appLayout.notifications.empty"),
|
|
609
|
-
compact: true
|
|
616
|
+
compact: true,
|
|
610
617
|
});
|
|
611
|
-
this.#notification_box_empty.appendChild(this.#empty_state.set())
|
|
612
|
-
this.#notification_box_main.appendChild(this.#notification_box_empty)
|
|
613
|
-
this.#notification_box.appendChild(this.#notification_box_header)
|
|
614
|
-
this.#notification_box.appendChild(this.#notification_box_main)
|
|
618
|
+
this.#notification_box_empty.appendChild(this.#empty_state.set());
|
|
619
|
+
this.#notification_box_main.appendChild(this.#notification_box_empty);
|
|
620
|
+
this.#notification_box.appendChild(this.#notification_box_header);
|
|
621
|
+
this.#notification_box.appendChild(this.#notification_box_main);
|
|
615
622
|
// Карточки кладёт снаружи Notification, поэтому счётчик, группы и пустое состояние
|
|
616
623
|
// следим по списку. Разделители дней ящик вставляет сам: на них реакция не нужна,
|
|
617
624
|
// иначе пересборка дёргала бы сама себя без конца
|
|
@@ -623,7 +630,7 @@ class NotificationBox {
|
|
|
623
630
|
this.#notification_button.addEventListener("click", () => {
|
|
624
631
|
if (this.#notification_button.classList.contains("notification_button_active")) this.#closePanel();
|
|
625
632
|
else this.#openPanel();
|
|
626
|
-
})
|
|
633
|
+
});
|
|
627
634
|
// Значок — украшение: без подписи имя кнопки складывалось из лигатуры («notifications»)
|
|
628
635
|
const notification_glyph = new Icon(Icons.SOCIAL.NOTIFICATIONS, "var(--header_icon_size)").set();
|
|
629
636
|
notification_glyph.setAttribute("aria-hidden", "true");
|
|
@@ -649,20 +656,21 @@ class NotificationBox {
|
|
|
649
656
|
event.preventDefault();
|
|
650
657
|
this.#closePanel();
|
|
651
658
|
});
|
|
652
|
-
header.set().addEventListener(
|
|
659
|
+
header.set().addEventListener("click", (e) => {
|
|
653
660
|
if (e.target !== this.#notification_button) this.#closePanel();
|
|
654
|
-
})
|
|
655
|
-
center.addEventListener(
|
|
661
|
+
});
|
|
662
|
+
center.addEventListener("click", (e) => {
|
|
656
663
|
if (!this.#notification_box.contains(e.target)) {
|
|
657
|
-
if (this.#notification_box.style.transform === `translateX(-${this.#notification_box_width_test}px)`)
|
|
664
|
+
if (this.#notification_box.style.transform === `translateX(-${this.#notification_box_width_test}px)`)
|
|
665
|
+
this.#closePanel();
|
|
658
666
|
}
|
|
659
|
-
})
|
|
667
|
+
});
|
|
660
668
|
center.onscroll = () => {
|
|
661
669
|
// Прокрутка меняет материал шапки: плотнее фон и тень. Класс, а не инлайновые стили:
|
|
662
670
|
// вид полосы целиком описан в global_style.css
|
|
663
671
|
header.set().classList.toggle("header_scrolled", center.scrollTop > 15);
|
|
664
672
|
};
|
|
665
|
-
header.addToRight(this.#notification_button)
|
|
673
|
+
header.addToRight(this.#notification_button);
|
|
666
674
|
}
|
|
667
675
|
#openPanel() {
|
|
668
676
|
this.#notification_box.style.transform = `translateX(-${this.#notification_box_width_test}px)`;
|
|
@@ -693,7 +701,8 @@ class NotificationBox {
|
|
|
693
701
|
*/
|
|
694
702
|
#onBoxMutated(records = []) {
|
|
695
703
|
const changed = records.some((record) =>
|
|
696
|
-
[...record.addedNodes, ...record.removedNodes].some((node) => node.tagName === "NOTIFICATION")
|
|
704
|
+
[...record.addedNodes, ...record.removedNodes].some((node) => node.tagName === "NOTIFICATION")
|
|
705
|
+
);
|
|
697
706
|
if (changed) this.#syncState();
|
|
698
707
|
}
|
|
699
708
|
// Счётчик, пустое состояние, группы дней и доступность кнопки согласованы с составом ящика
|
|
@@ -730,7 +739,8 @@ class NotificationBox {
|
|
|
730
739
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
731
740
|
const group = document.createElement("notification_group");
|
|
732
741
|
if (day === chui_functions.getDayKey()) group.innerText = I18n.t("appLayout.notifications.today");
|
|
733
|
-
else if (day === chui_functions.getDayKey(yesterday))
|
|
742
|
+
else if (day === chui_functions.getDayKey(yesterday))
|
|
743
|
+
group.innerText = I18n.t("appLayout.notifications.yesterday");
|
|
734
744
|
else {
|
|
735
745
|
// Ключ хранится как ГГГГ-ММ-ДД, а в подписи порядок привычный: ДД.ММ.ГГГГ
|
|
736
746
|
const [year, month, date] = String(day).split("-");
|
|
@@ -748,9 +758,10 @@ class NotificationBox {
|
|
|
748
758
|
#syncUnread(notifications = []) {
|
|
749
759
|
const unread = notifications.filter((notification) => notification.dataset.unread === "true").length;
|
|
750
760
|
this.#notification_button.classList.toggle("notification_button_unread", unread > 0);
|
|
751
|
-
const label =
|
|
752
|
-
|
|
753
|
-
|
|
761
|
+
const label =
|
|
762
|
+
unread === 0
|
|
763
|
+
? I18n.t("appLayout.notifications.title")
|
|
764
|
+
: I18n.t("appLayout.notifications.unreadCount", { count: unread });
|
|
754
765
|
this.#notification_button.setAttribute("aria-label", label);
|
|
755
766
|
this.#notification_button.setAttribute("title", label);
|
|
756
767
|
}
|
|
@@ -760,9 +771,9 @@ class NotificationBox {
|
|
|
760
771
|
}
|
|
761
772
|
#clearAll() {
|
|
762
773
|
for (const notification of this.#notifications()) {
|
|
763
|
-
notification.style.transform = "translateX(100%)"
|
|
764
|
-
notification.style.opacity = "0"
|
|
765
|
-
setTimeout(() => notification.remove(), 300)
|
|
774
|
+
notification.style.transform = "translateX(100%)";
|
|
775
|
+
notification.style.opacity = "0";
|
|
776
|
+
setTimeout(() => notification.remove(), 300);
|
|
766
777
|
}
|
|
767
778
|
}
|
|
768
779
|
getBox() {
|
|
@@ -776,7 +787,7 @@ class AppLayout {
|
|
|
776
787
|
#center = new Center(this.#layout).set();
|
|
777
788
|
#appMenu = new AppMenu(this.#header, this.#center, this.#layout);
|
|
778
789
|
#notificationBox = new NotificationBox(this.#header, this.#center, this.#layout);
|
|
779
|
-
#notificationPanel = document.createElement(
|
|
790
|
+
#notificationPanel = document.createElement("notification_panel");
|
|
780
791
|
// Текущие уведомления: ipc-обработчики регистрируются один раз и работают с последним
|
|
781
792
|
#downloadNotification = undefined;
|
|
782
793
|
#updateNotificationLoad = undefined;
|
|
@@ -785,18 +796,22 @@ class AppLayout {
|
|
|
785
796
|
// Слушатели живут весь процесс — обработчик ошибок обязан быть на месте всегда.
|
|
786
797
|
#onWindowError = (event) => {
|
|
787
798
|
const place = event.filename === undefined ? "" : ` (${event.filename}:${event.lineno})`;
|
|
788
|
-
this.#logError(
|
|
789
|
-
|
|
799
|
+
this.#logError(
|
|
800
|
+
I18n.t("appLayout.error.uncaught"),
|
|
801
|
+
event.error === undefined ? event.message : event.error,
|
|
802
|
+
place
|
|
803
|
+
);
|
|
804
|
+
};
|
|
790
805
|
#onUnhandledRejection = (event) => {
|
|
791
|
-
this.#logError(I18n.t("appLayout.error.unhandledRejection"), event.reason)
|
|
792
|
-
}
|
|
806
|
+
this.#logError(I18n.t("appLayout.error.unhandledRejection"), event.reason);
|
|
807
|
+
};
|
|
793
808
|
#logError(prefix, value, place = "") {
|
|
794
809
|
const text = `${prefix}: ${AppLayout.#formatError(value)}${place}`;
|
|
795
810
|
try {
|
|
796
|
-
Log.error(text)
|
|
811
|
+
Log.error(text);
|
|
797
812
|
} catch (e) {
|
|
798
813
|
// Сам обработчик ошибок падать не должен (например, ipcRenderer уже недоступен при закрытии окна)
|
|
799
|
-
console.error(text, e)
|
|
814
|
+
console.error(text, e);
|
|
800
815
|
}
|
|
801
816
|
}
|
|
802
817
|
static #formatError(value) {
|
|
@@ -805,10 +820,10 @@ class AppLayout {
|
|
|
805
820
|
return String(value);
|
|
806
821
|
}
|
|
807
822
|
constructor() {
|
|
808
|
-
require(
|
|
823
|
+
require("../../modules/chui_fonts").install();
|
|
809
824
|
// Глобальные стили
|
|
810
|
-
chui_functions.setStyles(__dirname + `/global_style.css`,
|
|
811
|
-
chui_functions.setStyles(__dirname + `/main_theme_style.css`,
|
|
825
|
+
chui_functions.setStyles(__dirname + `/global_style.css`, "chUiJS_Global_App");
|
|
826
|
+
chui_functions.setStyles(__dirname + `/main_theme_style.css`, "chUiJS_Main_Theme");
|
|
812
827
|
// Необработанные ошибки и reject’ы renderer-процесса — один раз на процесс
|
|
813
828
|
if (app_error_handlers_installed === false) {
|
|
814
829
|
app_error_handlers_installed = true;
|
|
@@ -818,11 +833,11 @@ class AppLayout {
|
|
|
818
833
|
//if (process.platform !== "darwin") document.body.style.border = "1px solid var(--border_color)";
|
|
819
834
|
// ===
|
|
820
835
|
|
|
821
|
-
document.body.appendChild(this.#header.set())
|
|
822
|
-
document.body.appendChild(this.#center)
|
|
823
|
-
document.body.appendChild(this.#appMenu.getMenu())
|
|
824
|
-
document.body.appendChild(this.#notificationBox.getBox())
|
|
825
|
-
document.body.appendChild(this.#notificationPanel)
|
|
836
|
+
document.body.appendChild(this.#header.set());
|
|
837
|
+
document.body.appendChild(this.#center);
|
|
838
|
+
document.body.appendChild(this.#appMenu.getMenu());
|
|
839
|
+
document.body.appendChild(this.#notificationBox.getBox());
|
|
840
|
+
document.body.appendChild(this.#notificationPanel);
|
|
826
841
|
// Части layout'а зарегистрированы: уведомления и Route ищут свои узлы по ролям, а не по id
|
|
827
842
|
this.#layout.set(LAYOUT_ROLE.NOTIFICATION_PANEL, this.#notificationPanel);
|
|
828
843
|
registerLayout(this.#layout);
|
|
@@ -837,41 +852,43 @@ class AppLayout {
|
|
|
837
852
|
ipcRenderer.on("sendNotificationDownload", (e, text, body) => {
|
|
838
853
|
this.#downloadNotification = new DownloadNotification({ title: text, text: body });
|
|
839
854
|
this.#downloadNotification.show();
|
|
840
|
-
})
|
|
855
|
+
});
|
|
841
856
|
ipcRenderer.on("sendNotificationDownloadUpdate", (e, title, text) => {
|
|
842
857
|
if (this.#downloadNotification !== undefined) this.#downloadNotification.update(title, text);
|
|
843
|
-
})
|
|
858
|
+
});
|
|
844
859
|
ipcRenderer.on("sendNotificationDownloadComplete", () => {
|
|
845
860
|
if (this.#downloadNotification !== undefined) this.#downloadNotification.done();
|
|
846
|
-
})
|
|
861
|
+
});
|
|
847
862
|
ipcRenderer.on("sendNotificationDownloadError", () => {
|
|
848
863
|
if (this.#downloadNotification !== undefined) this.#downloadNotification.error();
|
|
849
|
-
})
|
|
864
|
+
});
|
|
850
865
|
//
|
|
851
866
|
ipcRenderer.on("sendNotificationUpdateLoad", (e, text, body) => {
|
|
852
867
|
this.#updateNotificationLoad = new UpdateNotification({ title: text, text: body, spinner: true });
|
|
853
868
|
this.#updateNotificationLoad.show(true);
|
|
854
|
-
})
|
|
869
|
+
});
|
|
855
870
|
ipcRenderer.on("sendNotificationUpdateLoadClose", () => {
|
|
856
871
|
if (this.#updateNotificationLoad !== undefined) this.#updateNotificationLoad.hide();
|
|
857
|
-
})
|
|
872
|
+
});
|
|
858
873
|
ipcRenderer.on("sendNotificationUpdate", (e, text, body) => {
|
|
859
874
|
this.#updateNotification = new UpdateNotification({ title: text, text: body, spinner: false });
|
|
860
875
|
this.#updateNotification.show(true);
|
|
861
|
-
})
|
|
876
|
+
});
|
|
862
877
|
ipcRenderer.on("sendNotificationUpdateClose", () => {
|
|
863
878
|
if (this.#updateNotification !== undefined) this.#updateNotification.hide();
|
|
864
|
-
})
|
|
879
|
+
});
|
|
865
880
|
let popup = new Popup();
|
|
866
881
|
ipcRenderer.once("checkUpdatesTrue", async (e, check, version) => {
|
|
867
882
|
if (check) {
|
|
868
883
|
let confirm_res = await popup.confirm({
|
|
869
|
-
title: I18n.t("appLayout.update.available", {version: version}),
|
|
870
|
-
|
|
884
|
+
title: I18n.t("appLayout.update.available", { version: version }),
|
|
885
|
+
message: I18n.t("appLayout.update.question"),
|
|
886
|
+
cancelText: I18n.t("appLayout.update.cancel"),
|
|
887
|
+
okText: I18n.t("appLayout.update.install"),
|
|
871
888
|
});
|
|
872
|
-
e.sender.send("updateInstallConfirm", confirm_res)
|
|
889
|
+
e.sender.send("updateInstallConfirm", confirm_res);
|
|
873
890
|
}
|
|
874
|
-
})
|
|
891
|
+
});
|
|
875
892
|
}
|
|
876
893
|
setWindowControlsLeft() {
|
|
877
894
|
this.#header.addWC(true);
|
|
@@ -898,64 +915,84 @@ class AppLayout {
|
|
|
898
915
|
this.#header.addToLeftBeforeTitle(...headerLeft);
|
|
899
916
|
}
|
|
900
917
|
disableAppMenu() {
|
|
901
|
-
this.#layout.get(LAYOUT_ROLE.APP_MENU_BUTTON).remove()
|
|
918
|
+
this.#layout.get(LAYOUT_ROLE.APP_MENU_BUTTON).remove();
|
|
902
919
|
// Отступ имени страницы считает CSS: `.page_name_no_menu` в global_style.css
|
|
903
|
-
this.#layout.get(LAYOUT_ROLE.PAGE_NAME).classList.add("page_name_no_menu")
|
|
920
|
+
this.#layout.get(LAYOUT_ROLE.PAGE_NAME).classList.add("page_name_no_menu");
|
|
904
921
|
}
|
|
905
922
|
setScript(pathToJS = String(), id = String()) {
|
|
906
|
-
let data = fs.readFileSync(pathToJS,
|
|
907
|
-
let script = document.createElement(
|
|
923
|
+
let data = fs.readFileSync(pathToJS, "utf8");
|
|
924
|
+
let script = document.createElement("script");
|
|
908
925
|
script.innerHTML = String(data);
|
|
909
|
-
script.setAttribute(
|
|
926
|
+
script.setAttribute("id", id);
|
|
910
927
|
// Скрипт подключается в сам документ (не узел компонента), поэтому проверка по id и document
|
|
911
928
|
if (document.querySelector(`script[id="${id}"]`) === null) document.body.appendChild(script);
|
|
912
929
|
}
|
|
913
|
-
static TABS(
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
static USER_PROFILE(options = {
|
|
925
|
-
username: String(),
|
|
926
|
-
image: { noImage: Boolean(), imageLink: String(), imageBase64: String(), path: String() },
|
|
927
|
-
items: []
|
|
928
|
-
}) { return new UserProfile(options).set(); }
|
|
929
|
-
static USER_PROFILE_ITEM(options = {
|
|
930
|
-
title: String(),
|
|
931
|
-
icon: undefined,
|
|
932
|
-
clickEvent: () => {}
|
|
933
|
-
}) { return new UserDDItem(options).set(); }
|
|
934
|
-
static DIALOG(options = {
|
|
935
|
-
title: String(),
|
|
936
|
-
icon: undefined,
|
|
937
|
-
reverse: Boolean(),
|
|
938
|
-
label: String(),
|
|
939
|
-
dialogOptions: {
|
|
930
|
+
static TABS(
|
|
931
|
+
options = {
|
|
932
|
+
width: String(),
|
|
933
|
+
default: Number(),
|
|
934
|
+
tabs: [],
|
|
935
|
+
}
|
|
936
|
+
) {
|
|
937
|
+
return new HeaderTabs(options).set();
|
|
938
|
+
}
|
|
939
|
+
static BUTTON(
|
|
940
|
+
options = {
|
|
940
941
|
title: String(),
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
942
|
+
icon: undefined,
|
|
943
|
+
reverse: Boolean(),
|
|
944
|
+
clickEvent: () => {},
|
|
945
|
+
}
|
|
946
|
+
) {
|
|
947
|
+
return new HeaderButton(options).set();
|
|
948
|
+
}
|
|
949
|
+
static USER_PROFILE(
|
|
950
|
+
options = {
|
|
951
|
+
username: String(),
|
|
952
|
+
image: { noImage: Boolean(), imageLink: String(), imageBase64: String(), path: String() },
|
|
953
|
+
items: [],
|
|
954
|
+
}
|
|
955
|
+
) {
|
|
956
|
+
return new UserProfile(options).set();
|
|
957
|
+
}
|
|
958
|
+
static USER_PROFILE_ITEM(
|
|
959
|
+
options = {
|
|
960
|
+
title: String(),
|
|
961
|
+
icon: undefined,
|
|
962
|
+
clickEvent: () => {},
|
|
963
|
+
}
|
|
964
|
+
) {
|
|
965
|
+
return new UserDDItem(options).set();
|
|
966
|
+
}
|
|
967
|
+
static DIALOG(
|
|
968
|
+
options = {
|
|
969
|
+
title: String(),
|
|
970
|
+
icon: undefined,
|
|
971
|
+
reverse: Boolean(),
|
|
972
|
+
label: String(),
|
|
973
|
+
dialogOptions: {
|
|
974
|
+
title: String(),
|
|
975
|
+
closeOutSideClick: Boolean(),
|
|
976
|
+
style: {
|
|
977
|
+
width: String(),
|
|
978
|
+
height: String(),
|
|
979
|
+
direction: String(),
|
|
980
|
+
wrap: String(),
|
|
981
|
+
align: String(),
|
|
982
|
+
justify: String(),
|
|
983
|
+
},
|
|
984
|
+
components: [],
|
|
949
985
|
},
|
|
950
|
-
components: []
|
|
951
986
|
}
|
|
952
|
-
|
|
987
|
+
) {
|
|
988
|
+
return new HeaderDialog(options).set();
|
|
989
|
+
}
|
|
953
990
|
}
|
|
954
991
|
|
|
955
992
|
class HeaderTabs {
|
|
956
993
|
#id_list = require("randomstring").generate();
|
|
957
994
|
#header_tabs_list = undefined;
|
|
958
|
-
#header_tabs = document.createElement(
|
|
995
|
+
#header_tabs = document.createElement("header_tabs");
|
|
959
996
|
#keydown_event = (event) => this.#onKeydown(event);
|
|
960
997
|
constructor(options = { width: String(), default: Number(), tabs: [] }) {
|
|
961
998
|
this.#header_tabs_list = options.tabs;
|
|
@@ -968,13 +1005,13 @@ class HeaderTabs {
|
|
|
968
1005
|
item.setAttribute("role", "tab");
|
|
969
1006
|
item.setAttribute("tabindex", "-1");
|
|
970
1007
|
this.#header_tabs.appendChild(item);
|
|
971
|
-
item.addEventListener(
|
|
1008
|
+
item.addEventListener("click", (event) => {
|
|
972
1009
|
if (event.target.tagName === "HEADER_BUTTON_ICON" || event.target.tagName === "HEADER_BUTTON_TITLE") {
|
|
973
|
-
this.#setActive(event.target.parentNode)
|
|
1010
|
+
this.#setActive(event.target.parentNode);
|
|
974
1011
|
} else {
|
|
975
|
-
this.#setActive(event.target)
|
|
1012
|
+
this.#setActive(event.target);
|
|
976
1013
|
}
|
|
977
|
-
})
|
|
1014
|
+
});
|
|
978
1015
|
}
|
|
979
1016
|
// Клик по выбранной вкладке не переигрывает её действие — как у пункта меню текущего
|
|
980
1017
|
// раздела (`route_active`, который тоже не переходит повторно). Раньше клик до вкладки
|
|
@@ -982,11 +1019,15 @@ class HeaderTabs {
|
|
|
982
1019
|
// кликом он снимал у неё и курсор-указатель, и ховер, которых у соседних кнопок шапки
|
|
983
1020
|
// нет. Слушатель ловит клик в фазе перехвата: действие вкладки и `#setActive` висят
|
|
984
1021
|
// на самом элементе и иначе сработали бы первыми
|
|
985
|
-
this.#header_tabs.addEventListener(
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1022
|
+
this.#header_tabs.addEventListener(
|
|
1023
|
+
"click",
|
|
1024
|
+
(event) => {
|
|
1025
|
+
const tab = event.target.closest === undefined ? null : event.target.closest("header_button");
|
|
1026
|
+
if (tab === null || tab.getAttribute("active") === null) return;
|
|
1027
|
+
event.stopImmediatePropagation();
|
|
1028
|
+
},
|
|
1029
|
+
true
|
|
1030
|
+
);
|
|
990
1031
|
// Пока ни одна вкладка не выбрана, остановкой обхода становится первая по счёту:
|
|
991
1032
|
// иначе в полосу нельзя было бы войти с клавиатуры вообще
|
|
992
1033
|
const default_tab = this.#header_tabs_list[options.default === undefined ? 0 : options.default];
|
|
@@ -996,9 +1037,9 @@ class HeaderTabs {
|
|
|
996
1037
|
for (let item of this.#header_tabs_list) {
|
|
997
1038
|
if (this.#header_tabs_list.indexOf(item) === options.default) {
|
|
998
1039
|
setTimeout(() => {
|
|
999
|
-
item.click()
|
|
1040
|
+
item.click();
|
|
1000
1041
|
//this.#setActive(item)
|
|
1001
|
-
}, 250)
|
|
1042
|
+
}, 250);
|
|
1002
1043
|
}
|
|
1003
1044
|
}
|
|
1004
1045
|
}
|
|
@@ -1009,13 +1050,13 @@ class HeaderTabs {
|
|
|
1009
1050
|
if (target.getAttribute("active") === null) {
|
|
1010
1051
|
// `children`, а не `childNodes`: в `childNodes` попадают и текстовые узлы, у которых нет classList.
|
|
1011
1052
|
// `children` — HTMLCollection, поэтому перебираем копию
|
|
1012
|
-
[...this.#header_tabs.children].forEach(child => {
|
|
1053
|
+
[...this.#header_tabs.children].forEach((child) => {
|
|
1013
1054
|
child.removeAttribute("active");
|
|
1014
1055
|
child.removeAttribute("aria-current");
|
|
1015
1056
|
child.removeAttribute("aria-selected");
|
|
1016
1057
|
child.setAttribute("tabindex", "-1");
|
|
1017
|
-
child.classList.remove("header_tab_active")
|
|
1018
|
-
})
|
|
1058
|
+
child.classList.remove("header_tab_active");
|
|
1059
|
+
});
|
|
1019
1060
|
target.setAttribute("active", true);
|
|
1020
1061
|
// Вкладка без `active` была только подсвечена классом: скринридер о выборе не знал (как у пункта меню маршрутов)
|
|
1021
1062
|
target.setAttribute("aria-current", "page");
|
|
@@ -1023,7 +1064,7 @@ class HeaderTabs {
|
|
|
1023
1064
|
target.setAttribute("aria-selected", "true");
|
|
1024
1065
|
// Выбранная вкладка — единственная остановка Tab в полосе вкладок
|
|
1025
1066
|
target.setAttribute("tabindex", "0");
|
|
1026
|
-
target.classList.add("header_tab_active")
|
|
1067
|
+
target.classList.add("header_tab_active");
|
|
1027
1068
|
}
|
|
1028
1069
|
}
|
|
1029
1070
|
/*
|
|
@@ -1040,7 +1081,7 @@ class HeaderTabs {
|
|
|
1040
1081
|
if (tabs.length === 0) return;
|
|
1041
1082
|
const focused = tabs.indexOf(document.activeElement);
|
|
1042
1083
|
// Фокус мог быть не в полосе: тогда считаем от вкладки с остановкой обхода
|
|
1043
|
-
const from = focused === -1 ? tabs.findIndex(tab => tab.getAttribute("tabindex") === "0") : focused;
|
|
1084
|
+
const from = focused === -1 ? tabs.findIndex((tab) => tab.getAttribute("tabindex") === "0") : focused;
|
|
1044
1085
|
const start = from === -1 ? 0 : from;
|
|
1045
1086
|
const last = tabs.length - 1;
|
|
1046
1087
|
let next;
|
|
@@ -1075,23 +1116,25 @@ class UserProfile {
|
|
|
1075
1116
|
#window_click_event = (event) => {
|
|
1076
1117
|
if (this.#user_main.contains(event.target) || this.#user_dropdown.contains(event.target)) return;
|
|
1077
1118
|
this.#closeMenu();
|
|
1078
|
-
}
|
|
1119
|
+
};
|
|
1079
1120
|
// Выбор пункта закрывает меню. Обработчик на списке, а не на каждом пункте: пункты
|
|
1080
1121
|
// приходят из UserDDItem и могут быть добавлены кем угодно снаружи
|
|
1081
1122
|
#dropdown_click_event = (event) => {
|
|
1082
1123
|
const item = event.target.closest === undefined ? null : event.target.closest("user_item");
|
|
1083
1124
|
if (item === null || !this.#user_dropdown.contains(item)) return;
|
|
1084
1125
|
this.#closeMenu();
|
|
1085
|
-
}
|
|
1126
|
+
};
|
|
1086
1127
|
//
|
|
1087
|
-
constructor(
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1128
|
+
constructor(
|
|
1129
|
+
options = {
|
|
1130
|
+
username: String(),
|
|
1131
|
+
image: { noImage: Boolean(), imageLink: String(), imageBase64: String(), path: String() },
|
|
1132
|
+
items: [],
|
|
1133
|
+
}
|
|
1134
|
+
) {
|
|
1092
1135
|
this.#username = options.username;
|
|
1093
1136
|
this.#user_button.innerText = options.username;
|
|
1094
|
-
this.#user_main.appendChild(this.#user_button)
|
|
1137
|
+
this.#user_main.appendChild(this.#user_button);
|
|
1095
1138
|
UserProfile.#instances.set(this.#user_main, this);
|
|
1096
1139
|
// Панель — слой в body: внутри шапки (у неё свой backdrop-filter) её blur не работал бы,
|
|
1097
1140
|
// а выдвижные слои оболочки перекрывали бы её. Идентификатор нужен для aria-controls
|
|
@@ -1100,7 +1143,7 @@ class UserProfile {
|
|
|
1100
1143
|
this.#user_button.addEventListener("click", () => {
|
|
1101
1144
|
if (this.#is_open) this.#closeMenu();
|
|
1102
1145
|
else this.#openMenu();
|
|
1103
|
-
})
|
|
1146
|
+
});
|
|
1104
1147
|
//
|
|
1105
1148
|
if (options.image !== undefined) {
|
|
1106
1149
|
// Имя пользователя уже есть в кнопке и в пунктах меню, поэтому аватар — украшение
|
|
@@ -1108,7 +1151,7 @@ class UserProfile {
|
|
|
1108
1151
|
name: options.username,
|
|
1109
1152
|
image: options.image,
|
|
1110
1153
|
size: "80px",
|
|
1111
|
-
decorative: true
|
|
1154
|
+
decorative: true,
|
|
1112
1155
|
});
|
|
1113
1156
|
this.#user_dd_image_main.appendChild(this.#avatar.set());
|
|
1114
1157
|
this.#user_dropdown.appendChild(this.#user_dd_image_main);
|
|
@@ -1120,7 +1163,7 @@ class UserProfile {
|
|
|
1120
1163
|
this.#menuKeyboard = new MenuKeyboard(this.#user_dropdown, {
|
|
1121
1164
|
itemSelector: "user_item",
|
|
1122
1165
|
onEscape: () => this.#closeMenu(),
|
|
1123
|
-
trigger: this.#user_button
|
|
1166
|
+
trigger: this.#user_button,
|
|
1124
1167
|
});
|
|
1125
1168
|
makeKeyboardButton(this.#user_button);
|
|
1126
1169
|
// Закрытое меню объявляется закрытым сразу: иначе у кнопки не было бы aria-expanded
|
|
@@ -1213,11 +1256,13 @@ class UserProfile {
|
|
|
1213
1256
|
class UserDDItem {
|
|
1214
1257
|
#user_item = document.createElement("user_item");
|
|
1215
1258
|
|
|
1216
|
-
constructor(
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1259
|
+
constructor(
|
|
1260
|
+
options = {
|
|
1261
|
+
title: String(),
|
|
1262
|
+
icon: undefined,
|
|
1263
|
+
clickEvent: () => {},
|
|
1264
|
+
}
|
|
1265
|
+
) {
|
|
1221
1266
|
if (options.title !== undefined && options.icon !== undefined) {
|
|
1222
1267
|
this.#user_item.innerText = options.title;
|
|
1223
1268
|
// Значок ведёт строку — как в пунктах context_menu_item и выпадающих списков
|
|
@@ -1229,7 +1274,7 @@ class UserDDItem {
|
|
|
1229
1274
|
} else if (options.icon !== undefined) {
|
|
1230
1275
|
this.#user_item.innerHTML = new Icon(options.icon).getHTML();
|
|
1231
1276
|
}
|
|
1232
|
-
this.#user_item.addEventListener("click", options.clickEvent)
|
|
1277
|
+
this.#user_item.addEventListener("click", options.clickEvent);
|
|
1233
1278
|
}
|
|
1234
1279
|
|
|
1235
1280
|
set() {
|
|
@@ -1239,25 +1284,27 @@ class UserDDItem {
|
|
|
1239
1284
|
|
|
1240
1285
|
class HeaderDialog {
|
|
1241
1286
|
#header_button = undefined;
|
|
1242
|
-
constructor(
|
|
1243
|
-
|
|
1244
|
-
icon: undefined,
|
|
1245
|
-
reverse: Boolean(),
|
|
1246
|
-
label: String(),
|
|
1247
|
-
dialogOptions: {
|
|
1287
|
+
constructor(
|
|
1288
|
+
options = {
|
|
1248
1289
|
title: String(),
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1290
|
+
icon: undefined,
|
|
1291
|
+
reverse: Boolean(),
|
|
1292
|
+
label: String(),
|
|
1293
|
+
dialogOptions: {
|
|
1294
|
+
title: String(),
|
|
1295
|
+
closeOutSideClick: Boolean(),
|
|
1296
|
+
style: {
|
|
1297
|
+
width: String(),
|
|
1298
|
+
height: String(),
|
|
1299
|
+
direction: String(),
|
|
1300
|
+
wrap: String(),
|
|
1301
|
+
align: String(),
|
|
1302
|
+
justify: String(),
|
|
1303
|
+
},
|
|
1304
|
+
components: [],
|
|
1257
1305
|
},
|
|
1258
|
-
components: []
|
|
1259
1306
|
}
|
|
1260
|
-
|
|
1307
|
+
) {
|
|
1261
1308
|
// Неполные `dialogOptions` сборку не роняют: `style` и `components` — необязательные
|
|
1262
1309
|
// части опции, а прямое обращение к ним давало TypeError при создании кнопки
|
|
1263
1310
|
// («Cannot read properties of undefined») ещё до первого открытия слоя
|
|
@@ -1265,22 +1312,22 @@ class HeaderDialog {
|
|
|
1265
1312
|
const dialog_style = dialog_options.style === undefined ? {} : dialog_options.style;
|
|
1266
1313
|
const dialog_components = dialog_options.components === undefined ? [] : dialog_options.components;
|
|
1267
1314
|
// Пустой заголовок остаётся пустым: заготовка опции рисовала бы слово «undefined»
|
|
1268
|
-
const dialog_title =
|
|
1269
|
-
? "" : dialog_options.title;
|
|
1315
|
+
const dialog_title =
|
|
1316
|
+
dialog_options.title === undefined || dialog_options.title === null ? "" : dialog_options.title;
|
|
1270
1317
|
let dialog = new Dialog({
|
|
1271
1318
|
width: dialog_style.width,
|
|
1272
1319
|
height: dialog_style.height,
|
|
1273
1320
|
closeOutSideClick: dialog_options.closeOutSideClick,
|
|
1274
1321
|
// Имя модального слоя для скринридера: без него диалог объявлялся бы безымянным
|
|
1275
|
-
title: dialog_title
|
|
1276
|
-
})
|
|
1322
|
+
title: dialog_title,
|
|
1323
|
+
});
|
|
1277
1324
|
// HEADER
|
|
1278
1325
|
let header_dialog = new ContentBlock({
|
|
1279
1326
|
direction: "row",
|
|
1280
1327
|
wrap: "nowrap",
|
|
1281
1328
|
align: "center",
|
|
1282
1329
|
justify: "space-between",
|
|
1283
|
-
disableMarginChild: true
|
|
1330
|
+
disableMarginChild: true,
|
|
1284
1331
|
});
|
|
1285
1332
|
header_dialog.setWidth("-webkit-fill-available");
|
|
1286
1333
|
// Заголовок — как у заголовков панелей оболочки (размер `--font_default_size`, вес 500
|
|
@@ -1288,13 +1335,13 @@ class HeaderDialog {
|
|
|
1288
1335
|
let title = new Label({
|
|
1289
1336
|
markdownText: `**${dialog_title}**`,
|
|
1290
1337
|
wordBreak: "normal",
|
|
1291
|
-
width: "max-content"
|
|
1292
|
-
})
|
|
1338
|
+
width: "max-content",
|
|
1339
|
+
});
|
|
1293
1340
|
let close_button = new Button({
|
|
1294
1341
|
icon: Icons.NAVIGATION.CLOSE,
|
|
1295
1342
|
label: I18n.t("common.close"),
|
|
1296
1343
|
reverse: true,
|
|
1297
|
-
clickEvent: () => dialog.close()
|
|
1344
|
+
clickEvent: () => dialog.close(),
|
|
1298
1345
|
});
|
|
1299
1346
|
header_dialog.add(title, close_button);
|
|
1300
1347
|
dialog.addToHeader(header_dialog);
|
|
@@ -1303,10 +1350,10 @@ class HeaderDialog {
|
|
|
1303
1350
|
direction: dialog_style.direction,
|
|
1304
1351
|
wrap: dialog_style.wrap,
|
|
1305
1352
|
align: dialog_style.align,
|
|
1306
|
-
justify: dialog_style.justify
|
|
1353
|
+
justify: dialog_style.justify,
|
|
1307
1354
|
});
|
|
1308
1355
|
body_dialog.setWidth("-webkit-fill-available");
|
|
1309
|
-
body_dialog.add(...dialog_components)
|
|
1356
|
+
body_dialog.add(...dialog_components);
|
|
1310
1357
|
dialog.addToBody(body_dialog);
|
|
1311
1358
|
// FOOTER
|
|
1312
1359
|
|
|
@@ -1328,14 +1375,15 @@ class HeaderDialog {
|
|
|
1328
1375
|
class HeaderButton {
|
|
1329
1376
|
#header_button = undefined;
|
|
1330
1377
|
|
|
1331
|
-
constructor(
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1378
|
+
constructor(
|
|
1379
|
+
options = {
|
|
1380
|
+
title: String(),
|
|
1381
|
+
icon: undefined,
|
|
1382
|
+
reverse: Boolean(),
|
|
1383
|
+
label: String(),
|
|
1384
|
+
clickEvent: () => {},
|
|
1337
1385
|
}
|
|
1338
|
-
|
|
1386
|
+
) {
|
|
1339
1387
|
this.#header_button = makeHeaderButtonBox(options);
|
|
1340
1388
|
this.#header_button.addEventListener("click", options.clickEvent);
|
|
1341
1389
|
}
|
|
@@ -1345,5 +1393,5 @@ class HeaderButton {
|
|
|
1345
1393
|
}
|
|
1346
1394
|
}
|
|
1347
1395
|
|
|
1348
|
-
exports.AppLayout = AppLayout
|
|
1349
|
-
exports.Route = Route
|
|
1396
|
+
exports.AppLayout = AppLayout;
|
|
1397
|
+
exports.Route = Route;
|