chuijs 4.0.1 → 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 +113 -0
- package/README.md +43 -632
- package/framework/appLayout/default/chui_app_layout.js +314 -273
- package/framework/appLayout/default/global_style.css +93 -53
- 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 +56 -26
- 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_page/styles.css +3 -26
- 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 +76 -11
- package/framework/modules/chui_services/chui_services.js +39 -31
- package/framework/modules/chui_services/desktop.js +149 -30
- 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 +449 -334
- 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,28 +88,21 @@ class Route extends Events {
|
|
|
88
88
|
let center = requireLayoutNode(LAYOUT_ROLE.CENTER);
|
|
89
89
|
//
|
|
90
90
|
setPageName(page.getTitle());
|
|
91
|
-
center.innerHTML =
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const icon = page.getIcon();
|
|
100
|
-
if (icon !== undefined && icon !== null) block.innerHTML = String(icon);
|
|
101
|
-
block.appendChild(document.createTextNode(String(title)));
|
|
102
|
-
page_node.prepend(block);
|
|
103
|
-
}
|
|
104
|
-
center.appendChild(page_node);
|
|
105
|
-
const _page = center.querySelector('page');
|
|
91
|
+
center.innerHTML = "";
|
|
92
|
+
// Содержимое собирает сама страница (`Page#render`), оболочка в него не подмешивает
|
|
93
|
+
// ничего: узел `page_title` (был здесь) дублировал заголовок, который страница
|
|
94
|
+
// рисует сама, а `Page#render` отдаёт один и тот же узел — повторный переход на тот
|
|
95
|
+
// же экземпляр страницы (кнопка «Назад» из `Page#setBackButton`) добавлял второй
|
|
96
|
+
// заголовок. Место названия — шапка (`setPageName`).
|
|
97
|
+
center.appendChild(page.render());
|
|
98
|
+
const _page = center.querySelector("page");
|
|
106
99
|
new Animation(_page).fadeIn();
|
|
107
100
|
// Стирания `center.removeAttribute("style")` здесь больше нет: инлайновых
|
|
108
101
|
// стилей на `center` не ставит ни одна часть оболочки, а стирать чужое
|
|
109
102
|
// (если появится) — это ловушка, на которой уже терялись `display: flex`
|
|
110
103
|
applyPageMenuBar(requireLayoutNode(LAYOUT_ROLE.HEADER), page.getMenuBar());
|
|
111
104
|
|
|
112
|
-
this.route(page)
|
|
105
|
+
this.route(page);
|
|
113
106
|
}
|
|
114
107
|
}
|
|
115
108
|
|
|
@@ -168,18 +161,21 @@ function makeHeaderButtonBox(options = { title: undefined, icon: undefined, reve
|
|
|
168
161
|
* подобраны под вид набора; цвет наследуется от кнопки через `currentColor`.
|
|
169
162
|
*/
|
|
170
163
|
const WINDOW_ICONS = {
|
|
171
|
-
minimize:
|
|
172
|
-
|
|
173
|
-
|
|
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>',
|
|
174
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>',
|
|
175
171
|
};
|
|
176
172
|
|
|
177
173
|
class WindowControls {
|
|
178
174
|
// Управление кнопками
|
|
179
|
-
#box = document.createElement("wc_box")
|
|
180
|
-
#close = document.createElement("wc_close")
|
|
181
|
-
#maximize = document.createElement("wc_maximize")
|
|
182
|
-
#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");
|
|
183
179
|
// Подписка на нересайзящееся окно: set() вызывается и из конструктора Header, и из
|
|
184
180
|
// addWC(), поэтому в set() слушатель ipcRenderer накапливался.
|
|
185
181
|
#resizable_false_event = () => this.#maximize.remove();
|
|
@@ -195,15 +191,15 @@ class WindowControls {
|
|
|
195
191
|
this.#setButton(this.#minimize, I18n.t("appLayout.window.minimize"), "minimize");
|
|
196
192
|
// «Развернуть» — шеврон вверх, зеркало «свернуть»: пара читается как одна ось
|
|
197
193
|
this.#setButton(this.#maximize, I18n.t("appLayout.window.maximize"), "maximize");
|
|
198
|
-
this.#close.addEventListener("click", () => this.#windowAction("close"))
|
|
199
|
-
this.#minimize.addEventListener("click", () => this.#windowAction("minimize"))
|
|
194
|
+
this.#close.addEventListener("click", () => this.#windowAction("close"));
|
|
195
|
+
this.#minimize.addEventListener("click", () => this.#windowAction("minimize"));
|
|
200
196
|
// Разворот и восстановление — одно действие: решение принимает окно, а не кнопка
|
|
201
|
-
this.#maximize.addEventListener("click", () => this.#windowAction("toggle-maximize"))
|
|
197
|
+
this.#maximize.addEventListener("click", () => this.#windowAction("toggle-maximize"));
|
|
202
198
|
// Разворот приходит не только от нашей кнопки (двойной клик по полосе, меню окна),
|
|
203
199
|
// поэтому состояние читаем по факту изменения размера окна
|
|
204
|
-
window.addEventListener("resize", this.#sync_maximized_event)
|
|
205
|
-
this.#syncMaximized()
|
|
206
|
-
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);
|
|
207
203
|
}
|
|
208
204
|
/** Кнопка окна — значок с именем для скринридера, фокусом и активацией с клавиатуры.
|
|
209
205
|
* Значок рисуется линиями (`WINDOW_ICONS`), размер задаёт CSS (см. global_style.css) */
|
|
@@ -254,13 +250,13 @@ class WindowControls {
|
|
|
254
250
|
#sync_maximized_event = () => this.#syncMaximized();
|
|
255
251
|
set(pos_bool = Boolean()) {
|
|
256
252
|
if (pos_bool) {
|
|
257
|
-
this.#box.appendChild(this.#close)
|
|
258
|
-
this.#box.appendChild(this.#maximize)
|
|
259
|
-
this.#box.appendChild(this.#minimize)
|
|
253
|
+
this.#box.appendChild(this.#close);
|
|
254
|
+
this.#box.appendChild(this.#maximize);
|
|
255
|
+
this.#box.appendChild(this.#minimize);
|
|
260
256
|
} else {
|
|
261
|
-
this.#box.appendChild(this.#minimize)
|
|
262
|
-
this.#box.appendChild(this.#maximize)
|
|
263
|
-
this.#box.appendChild(this.#close)
|
|
257
|
+
this.#box.appendChild(this.#minimize);
|
|
258
|
+
this.#box.appendChild(this.#maximize);
|
|
259
|
+
this.#box.appendChild(this.#close);
|
|
264
260
|
}
|
|
265
261
|
return this.#box;
|
|
266
262
|
}
|
|
@@ -283,7 +279,7 @@ class Header {
|
|
|
283
279
|
this.#header_main.appendChild(this.#header_left_box);
|
|
284
280
|
this.#header_main.appendChild(this.#header_right_box);
|
|
285
281
|
this.#header_main.appendChild(this.#wc_box.set(false));
|
|
286
|
-
this.#header.appendChild(this.#header_main)
|
|
282
|
+
this.#header.appendChild(this.#header_main);
|
|
287
283
|
this.#observeHeight();
|
|
288
284
|
}
|
|
289
285
|
/*
|
|
@@ -323,7 +319,8 @@ class Header {
|
|
|
323
319
|
this.#header_main.insertBefore(this.#wc_box.set(boolean), this.#header_main.firstChild);
|
|
324
320
|
}
|
|
325
321
|
addToLeftBeforeTitle(...components) {
|
|
326
|
-
for (let component of components)
|
|
322
|
+
for (let component of components)
|
|
323
|
+
this.#header_left_box.insertBefore(component, this.#header_left_box.firstChild);
|
|
327
324
|
}
|
|
328
325
|
/** Прежнее имя с опечаткой (`Tittle`): оставлено синонимом, чтобы не ломать чужие вызовы */
|
|
329
326
|
addToLeftBeforeTittle(...components) {
|
|
@@ -331,11 +328,12 @@ class Header {
|
|
|
331
328
|
}
|
|
332
329
|
addToLeft(...components) {
|
|
333
330
|
for (let component of components) {
|
|
334
|
-
this.#header_left_box.appendChild(component)
|
|
331
|
+
this.#header_left_box.appendChild(component);
|
|
335
332
|
}
|
|
336
333
|
}
|
|
337
334
|
addToRight(...components) {
|
|
338
|
-
for (let component of components)
|
|
335
|
+
for (let component of components)
|
|
336
|
+
this.#header_right_box.insertBefore(component, this.#header_right_box.firstChild);
|
|
339
337
|
}
|
|
340
338
|
removeToRight(...components) {
|
|
341
339
|
for (let component of components) {
|
|
@@ -351,7 +349,7 @@ class Header {
|
|
|
351
349
|
}
|
|
352
350
|
|
|
353
351
|
class Center {
|
|
354
|
-
#center = document.createElement(
|
|
352
|
+
#center = document.createElement("center");
|
|
355
353
|
constructor(layout) {
|
|
356
354
|
this.#center.id = "center";
|
|
357
355
|
layout.set(LAYOUT_ROLE.CENTER, this.#center);
|
|
@@ -363,12 +361,14 @@ class Center {
|
|
|
363
361
|
|
|
364
362
|
class AppMenu {
|
|
365
363
|
#routeList = [];
|
|
366
|
-
#appMenu = document.createElement(
|
|
364
|
+
#appMenu = document.createElement("app_menu");
|
|
367
365
|
#appMenuWidth = 400;
|
|
368
366
|
#appMenuOffset = 25;
|
|
369
|
-
get #appMenuSlide() {
|
|
370
|
-
|
|
371
|
-
|
|
367
|
+
get #appMenuSlide() {
|
|
368
|
+
return this.#appMenuWidth + this.#appMenuOffset;
|
|
369
|
+
}
|
|
370
|
+
#routeViews = document.createElement("route_views");
|
|
371
|
+
#appMenuButton = document.createElement("app_menu_button");
|
|
372
372
|
// Шапка панели: плитка со значком и заголовок — панель читается выдвижным ящиком
|
|
373
373
|
#app_menu_header = document.createElement("app_menu_header");
|
|
374
374
|
#app_menu_header_icon = document.createElement("app_menu_header_icon");
|
|
@@ -386,7 +386,7 @@ class AppMenu {
|
|
|
386
386
|
#routeKeyboard = undefined;
|
|
387
387
|
#header = undefined;
|
|
388
388
|
#center = undefined;
|
|
389
|
-
#page_name = document.createElement(
|
|
389
|
+
#page_name = document.createElement("page_name");
|
|
390
390
|
constructor(header, center, layout) {
|
|
391
391
|
this.#header = header;
|
|
392
392
|
this.#center = center;
|
|
@@ -396,9 +396,9 @@ class AppMenu {
|
|
|
396
396
|
this.#appMenu.style.width = `${this.#appMenuWidth}px`;
|
|
397
397
|
this.#appMenu.style.left = `calc(-${this.#appMenuSlide}px)`;
|
|
398
398
|
//
|
|
399
|
-
this.#appMenuButton.id = "appMenuButton"
|
|
399
|
+
this.#appMenuButton.id = "appMenuButton";
|
|
400
400
|
// Список пунктов — тот узел, которым управляет кнопка: `aria-controls` ссылается на него
|
|
401
|
-
this.#routeViews.id = "chui_app_menu"
|
|
401
|
+
this.#routeViews.id = "chui_app_menu";
|
|
402
402
|
// Значок — украшение: без подписи имя кнопки складывалось из лигатуры («menu»)
|
|
403
403
|
this.#appMenuButton.setAttribute("aria-label", I18n.t("appLayout.menu"));
|
|
404
404
|
this.#appMenuButton.setAttribute("title", I18n.t("appLayout.menu"));
|
|
@@ -406,16 +406,16 @@ class AppMenu {
|
|
|
406
406
|
this.#appMenuButton.addEventListener("click", () => {
|
|
407
407
|
if (this.#appMenuButton.classList.contains("app_menu_button_active")) this.#close();
|
|
408
408
|
else this.#open();
|
|
409
|
-
})
|
|
410
|
-
header.set().addEventListener(
|
|
409
|
+
});
|
|
410
|
+
header.set().addEventListener("click", (e) => {
|
|
411
411
|
if (e.target !== this.#appMenuButton) this.#close();
|
|
412
|
-
})
|
|
413
|
-
center.addEventListener(
|
|
412
|
+
});
|
|
413
|
+
center.addEventListener("click", (e) => {
|
|
414
414
|
if (!this.#appMenu.contains(e.target)) {
|
|
415
415
|
if (this.#appMenu.style.transform === `translateX(${this.#appMenuSlide}px)`) this.#close();
|
|
416
416
|
}
|
|
417
|
-
})
|
|
418
|
-
this.#page_name.id = "page_name"
|
|
417
|
+
});
|
|
418
|
+
this.#page_name.id = "page_name";
|
|
419
419
|
layout.set(LAYOUT_ROLE.PAGE_NAME, this.#page_name);
|
|
420
420
|
header.addToLeft(this.#appMenuButton, this.#page_name);
|
|
421
421
|
this.#app_menu_header_icon.appendChild(new Icon(Icons.NAVIGATION.MENU_OPEN, "16px").set());
|
|
@@ -425,18 +425,18 @@ class AppMenu {
|
|
|
425
425
|
// Список пунктов назван головкой панели: скринридер читает «Меню», а не безымянный список
|
|
426
426
|
this.#app_menu_header_title.id = "chui_app_menu_title";
|
|
427
427
|
this.#routeViews.setAttribute("aria-labelledby", this.#app_menu_header_title.id);
|
|
428
|
-
this.#app_menu_header.appendChild(this.#app_menu_header_icon)
|
|
429
|
-
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);
|
|
430
430
|
this.#app_menu_empty.innerText = I18n.t("appLayout.search.empty");
|
|
431
431
|
// Подсказка лежит внутри списка, но пунктом не является: клавиатура меню её не видит (itemSelector "route")
|
|
432
|
-
this.#routeViews.appendChild(this.#app_menu_empty)
|
|
433
|
-
this.#appMenu.appendChild(this.#app_menu_header)
|
|
434
|
-
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);
|
|
435
435
|
// Клавиатура и aria меню маршрутов: стрелки по пунктам, Escape закрывает и возвращает фокус на кнопку
|
|
436
436
|
this.#routeKeyboard = new MenuKeyboard(this.#routeViews, {
|
|
437
437
|
itemSelector: "route",
|
|
438
438
|
onEscape: () => this.#close(),
|
|
439
|
-
trigger: this.#appMenuButton
|
|
439
|
+
trigger: this.#appMenuButton,
|
|
440
440
|
});
|
|
441
441
|
makeKeyboardButton(this.#appMenuButton);
|
|
442
442
|
// Закрытая панель объявляется закрытой сразу: иначе у кнопки не было бы aria-expanded,
|
|
@@ -473,18 +473,18 @@ class AppMenu {
|
|
|
473
473
|
enableSearchInput() {
|
|
474
474
|
if (this.#search_enabled) return;
|
|
475
475
|
this.#search_enabled = true;
|
|
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"))
|
|
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"));
|
|
479
479
|
// Подсветку поля даёт :focus-within на поверхности поиска — инлайновых стилей больше нет
|
|
480
480
|
this.#app_menu_search_input.addEventListener("input", (event) => {
|
|
481
|
-
this.#filterRoutes(event.target.value)
|
|
482
|
-
})
|
|
481
|
+
this.#filterRoutes(event.target.value);
|
|
482
|
+
});
|
|
483
483
|
this.#app_menu_search_icon.innerHTML = new Icon(Icons.ACTIONS.SEARCH, "15px").getHTML();
|
|
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)
|
|
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);
|
|
488
488
|
}
|
|
489
489
|
// Пункты списка: подсказка о пустом результате лежит там же, поэтому фильтруем по тегу
|
|
490
490
|
#routes() {
|
|
@@ -506,9 +506,9 @@ class AppMenu {
|
|
|
506
506
|
let center = this.#center;
|
|
507
507
|
//
|
|
508
508
|
setPageName(page.getTitle());
|
|
509
|
-
center.innerHTML =
|
|
509
|
+
center.innerHTML = "";
|
|
510
510
|
center.appendChild(page.render());
|
|
511
|
-
const _page = center.querySelector(
|
|
511
|
+
const _page = center.querySelector("page");
|
|
512
512
|
new Animation(_page).fadeIn();
|
|
513
513
|
// Стирания `center.removeAttribute("style")` здесь больше нет: инлайновых
|
|
514
514
|
// стилей на `center` не ставит ни одна часть оболочки, а стирать чужое
|
|
@@ -517,30 +517,30 @@ class AppMenu {
|
|
|
517
517
|
}
|
|
518
518
|
setRouteTest(page) {
|
|
519
519
|
this.#routeList.push(page);
|
|
520
|
-
let test = this.#routeList.filter(route => route.getTitle().includes(page.getTitle()));
|
|
520
|
+
let test = this.#routeList.filter((route) => route.getTitle().includes(page.getTitle()));
|
|
521
521
|
if (test.length === 1) {
|
|
522
|
-
let button_route = document.createElement(
|
|
523
|
-
let icon_menu = document.createElement(
|
|
522
|
+
let button_route = document.createElement("route");
|
|
523
|
+
let icon_menu = document.createElement("route_icon");
|
|
524
524
|
icon_menu.innerHTML = page.getIcon();
|
|
525
|
-
let title_menu = document.createElement(
|
|
525
|
+
let title_menu = document.createElement("route_title");
|
|
526
526
|
title_menu.innerHTML = page.getTitle();
|
|
527
527
|
if (page.getMain()) {
|
|
528
528
|
this.go(page);
|
|
529
529
|
button_route.classList.add("route_active");
|
|
530
530
|
}
|
|
531
|
-
button_route.addEventListener(
|
|
532
|
-
const eventAwesome = new CustomEvent("route_event_"+page.constructor.name, {
|
|
531
|
+
button_route.addEventListener("click", () => {
|
|
532
|
+
const eventAwesome = new CustomEvent("route_event_" + page.constructor.name, {
|
|
533
533
|
bubbles: true,
|
|
534
534
|
detail: {
|
|
535
535
|
class: page.constructor.name,
|
|
536
536
|
title: page.getTitle(),
|
|
537
|
-
page: page
|
|
537
|
+
page: page,
|
|
538
538
|
},
|
|
539
539
|
});
|
|
540
|
-
document.dispatchEvent(eventAwesome)
|
|
540
|
+
document.dispatchEvent(eventAwesome);
|
|
541
541
|
|
|
542
|
-
if (!button_route.classList.contains(
|
|
543
|
-
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");
|
|
544
544
|
this.go(page);
|
|
545
545
|
button_route.classList.add("route_active");
|
|
546
546
|
if (this.#auto_close) {
|
|
@@ -551,15 +551,15 @@ class AppMenu {
|
|
|
551
551
|
}
|
|
552
552
|
});
|
|
553
553
|
if (page.getIcon() !== undefined) {
|
|
554
|
-
button_route.appendChild(icon_menu)
|
|
554
|
+
button_route.appendChild(icon_menu);
|
|
555
555
|
}
|
|
556
|
-
button_route.appendChild(title_menu)
|
|
557
|
-
this.#routeViews.appendChild(button_route)
|
|
556
|
+
button_route.appendChild(title_menu);
|
|
557
|
+
this.#routeViews.appendChild(button_route);
|
|
558
558
|
this.#routeKeyboard.refresh();
|
|
559
559
|
// Новый пункт мог не пройти уже введённый фильтр — список и подсказка пересобираются
|
|
560
560
|
this.#filterRoutes(this.#app_menu_search_input.value);
|
|
561
561
|
} else {
|
|
562
|
-
Log.error(I18n.t("appLayout.search.alreadyAdded", {title: page.getTitle()}))
|
|
562
|
+
Log.error(I18n.t("appLayout.search.alreadyAdded", { title: page.getTitle() }));
|
|
563
563
|
}
|
|
564
564
|
}
|
|
565
565
|
setAutoClose() {
|
|
@@ -571,7 +571,7 @@ class AppMenu {
|
|
|
571
571
|
}
|
|
572
572
|
|
|
573
573
|
class NotificationBox {
|
|
574
|
-
#notification_box = document.createElement(
|
|
574
|
+
#notification_box = document.createElement("notification_box");
|
|
575
575
|
#notification_box_main = document.createElement("notification_box_main");
|
|
576
576
|
// Шапка ящика: заголовок со счётчиком истории и действие «Очистить»
|
|
577
577
|
#notification_box_header = document.createElement("notification_box_header");
|
|
@@ -585,16 +585,16 @@ class NotificationBox {
|
|
|
585
585
|
#observer = undefined;
|
|
586
586
|
#notification_box_width = 400;
|
|
587
587
|
#notification_box_width_test = 425;
|
|
588
|
-
#notification_button = document.createElement(
|
|
588
|
+
#notification_button = document.createElement("notification_button");
|
|
589
589
|
constructor(header, center, layout) {
|
|
590
590
|
layout.set(LAYOUT_ROLE.NOTIFICATION_BOX, this.#notification_box);
|
|
591
591
|
layout.set(LAYOUT_ROLE.NOTIFICATION_BOX_MAIN, this.#notification_box_main);
|
|
592
|
-
this.#notification_box_main.id =
|
|
592
|
+
this.#notification_box_main.id = "chui_notification_box";
|
|
593
593
|
// Шапка: заголовок, счётчик записей и очистка списка
|
|
594
594
|
this.#notification_box_title.innerText = I18n.t("appLayout.notifications.title");
|
|
595
595
|
this.#notification_box_count.setAttribute("aria-hidden", "true");
|
|
596
|
-
this.#notification_box_header_text.appendChild(this.#notification_box_title)
|
|
597
|
-
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);
|
|
598
598
|
// Очистка — обычная кнопка-действие, а не Button: в шапке нужна тихая ссылка-действие
|
|
599
599
|
this.#clear_button.className = "notification_box_clear";
|
|
600
600
|
this.#clear_button.type = "button";
|
|
@@ -603,22 +603,22 @@ class NotificationBox {
|
|
|
603
603
|
this.#clear_button.appendChild(new Icon(Icons.ACTIONS.DELETE, "14px").set());
|
|
604
604
|
const clear_title = document.createElement("span");
|
|
605
605
|
clear_title.innerText = I18n.t("appLayout.notifications.clear");
|
|
606
|
-
this.#clear_button.appendChild(clear_title)
|
|
606
|
+
this.#clear_button.appendChild(clear_title);
|
|
607
607
|
this.#clear_button.addEventListener("click", () => this.#clearAll());
|
|
608
|
-
this.#notification_box_header.appendChild(this.#notification_box_header_text)
|
|
609
|
-
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);
|
|
610
610
|
// Пустое состояние — общий компонент, а не своя разметка из значка и подписи: это был
|
|
611
611
|
// третий вариант одного и того же блока (здесь, в поиске меню, в таблицах). Заодно он
|
|
612
612
|
// объявляется скринридеру (role="status"), а прежняя заглушка молчала
|
|
613
613
|
this.#empty_state = new EmptyState({
|
|
614
614
|
icon: Icons.SOCIAL.NOTIFICATIONS_OFF,
|
|
615
615
|
title: I18n.t("appLayout.notifications.empty"),
|
|
616
|
-
compact: true
|
|
616
|
+
compact: true,
|
|
617
617
|
});
|
|
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)
|
|
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);
|
|
622
622
|
// Карточки кладёт снаружи Notification, поэтому счётчик, группы и пустое состояние
|
|
623
623
|
// следим по списку. Разделители дней ящик вставляет сам: на них реакция не нужна,
|
|
624
624
|
// иначе пересборка дёргала бы сама себя без конца
|
|
@@ -630,7 +630,7 @@ class NotificationBox {
|
|
|
630
630
|
this.#notification_button.addEventListener("click", () => {
|
|
631
631
|
if (this.#notification_button.classList.contains("notification_button_active")) this.#closePanel();
|
|
632
632
|
else this.#openPanel();
|
|
633
|
-
})
|
|
633
|
+
});
|
|
634
634
|
// Значок — украшение: без подписи имя кнопки складывалось из лигатуры («notifications»)
|
|
635
635
|
const notification_glyph = new Icon(Icons.SOCIAL.NOTIFICATIONS, "var(--header_icon_size)").set();
|
|
636
636
|
notification_glyph.setAttribute("aria-hidden", "true");
|
|
@@ -656,20 +656,21 @@ class NotificationBox {
|
|
|
656
656
|
event.preventDefault();
|
|
657
657
|
this.#closePanel();
|
|
658
658
|
});
|
|
659
|
-
header.set().addEventListener(
|
|
659
|
+
header.set().addEventListener("click", (e) => {
|
|
660
660
|
if (e.target !== this.#notification_button) this.#closePanel();
|
|
661
|
-
})
|
|
662
|
-
center.addEventListener(
|
|
661
|
+
});
|
|
662
|
+
center.addEventListener("click", (e) => {
|
|
663
663
|
if (!this.#notification_box.contains(e.target)) {
|
|
664
|
-
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();
|
|
665
666
|
}
|
|
666
|
-
})
|
|
667
|
+
});
|
|
667
668
|
center.onscroll = () => {
|
|
668
669
|
// Прокрутка меняет материал шапки: плотнее фон и тень. Класс, а не инлайновые стили:
|
|
669
670
|
// вид полосы целиком описан в global_style.css
|
|
670
671
|
header.set().classList.toggle("header_scrolled", center.scrollTop > 15);
|
|
671
672
|
};
|
|
672
|
-
header.addToRight(this.#notification_button)
|
|
673
|
+
header.addToRight(this.#notification_button);
|
|
673
674
|
}
|
|
674
675
|
#openPanel() {
|
|
675
676
|
this.#notification_box.style.transform = `translateX(-${this.#notification_box_width_test}px)`;
|
|
@@ -700,7 +701,8 @@ class NotificationBox {
|
|
|
700
701
|
*/
|
|
701
702
|
#onBoxMutated(records = []) {
|
|
702
703
|
const changed = records.some((record) =>
|
|
703
|
-
[...record.addedNodes, ...record.removedNodes].some((node) => node.tagName === "NOTIFICATION")
|
|
704
|
+
[...record.addedNodes, ...record.removedNodes].some((node) => node.tagName === "NOTIFICATION")
|
|
705
|
+
);
|
|
704
706
|
if (changed) this.#syncState();
|
|
705
707
|
}
|
|
706
708
|
// Счётчик, пустое состояние, группы дней и доступность кнопки согласованы с составом ящика
|
|
@@ -737,7 +739,8 @@ class NotificationBox {
|
|
|
737
739
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
738
740
|
const group = document.createElement("notification_group");
|
|
739
741
|
if (day === chui_functions.getDayKey()) group.innerText = I18n.t("appLayout.notifications.today");
|
|
740
|
-
else if (day === chui_functions.getDayKey(yesterday))
|
|
742
|
+
else if (day === chui_functions.getDayKey(yesterday))
|
|
743
|
+
group.innerText = I18n.t("appLayout.notifications.yesterday");
|
|
741
744
|
else {
|
|
742
745
|
// Ключ хранится как ГГГГ-ММ-ДД, а в подписи порядок привычный: ДД.ММ.ГГГГ
|
|
743
746
|
const [year, month, date] = String(day).split("-");
|
|
@@ -755,9 +758,10 @@ class NotificationBox {
|
|
|
755
758
|
#syncUnread(notifications = []) {
|
|
756
759
|
const unread = notifications.filter((notification) => notification.dataset.unread === "true").length;
|
|
757
760
|
this.#notification_button.classList.toggle("notification_button_unread", unread > 0);
|
|
758
|
-
const label =
|
|
759
|
-
|
|
760
|
-
|
|
761
|
+
const label =
|
|
762
|
+
unread === 0
|
|
763
|
+
? I18n.t("appLayout.notifications.title")
|
|
764
|
+
: I18n.t("appLayout.notifications.unreadCount", { count: unread });
|
|
761
765
|
this.#notification_button.setAttribute("aria-label", label);
|
|
762
766
|
this.#notification_button.setAttribute("title", label);
|
|
763
767
|
}
|
|
@@ -767,9 +771,9 @@ class NotificationBox {
|
|
|
767
771
|
}
|
|
768
772
|
#clearAll() {
|
|
769
773
|
for (const notification of this.#notifications()) {
|
|
770
|
-
notification.style.transform = "translateX(100%)"
|
|
771
|
-
notification.style.opacity = "0"
|
|
772
|
-
setTimeout(() => notification.remove(), 300)
|
|
774
|
+
notification.style.transform = "translateX(100%)";
|
|
775
|
+
notification.style.opacity = "0";
|
|
776
|
+
setTimeout(() => notification.remove(), 300);
|
|
773
777
|
}
|
|
774
778
|
}
|
|
775
779
|
getBox() {
|
|
@@ -783,7 +787,7 @@ class AppLayout {
|
|
|
783
787
|
#center = new Center(this.#layout).set();
|
|
784
788
|
#appMenu = new AppMenu(this.#header, this.#center, this.#layout);
|
|
785
789
|
#notificationBox = new NotificationBox(this.#header, this.#center, this.#layout);
|
|
786
|
-
#notificationPanel = document.createElement(
|
|
790
|
+
#notificationPanel = document.createElement("notification_panel");
|
|
787
791
|
// Текущие уведомления: ipc-обработчики регистрируются один раз и работают с последним
|
|
788
792
|
#downloadNotification = undefined;
|
|
789
793
|
#updateNotificationLoad = undefined;
|
|
@@ -792,18 +796,22 @@ class AppLayout {
|
|
|
792
796
|
// Слушатели живут весь процесс — обработчик ошибок обязан быть на месте всегда.
|
|
793
797
|
#onWindowError = (event) => {
|
|
794
798
|
const place = event.filename === undefined ? "" : ` (${event.filename}:${event.lineno})`;
|
|
795
|
-
this.#logError(
|
|
796
|
-
|
|
799
|
+
this.#logError(
|
|
800
|
+
I18n.t("appLayout.error.uncaught"),
|
|
801
|
+
event.error === undefined ? event.message : event.error,
|
|
802
|
+
place
|
|
803
|
+
);
|
|
804
|
+
};
|
|
797
805
|
#onUnhandledRejection = (event) => {
|
|
798
|
-
this.#logError(I18n.t("appLayout.error.unhandledRejection"), event.reason)
|
|
799
|
-
}
|
|
806
|
+
this.#logError(I18n.t("appLayout.error.unhandledRejection"), event.reason);
|
|
807
|
+
};
|
|
800
808
|
#logError(prefix, value, place = "") {
|
|
801
809
|
const text = `${prefix}: ${AppLayout.#formatError(value)}${place}`;
|
|
802
810
|
try {
|
|
803
|
-
Log.error(text)
|
|
811
|
+
Log.error(text);
|
|
804
812
|
} catch (e) {
|
|
805
813
|
// Сам обработчик ошибок падать не должен (например, ipcRenderer уже недоступен при закрытии окна)
|
|
806
|
-
console.error(text, e)
|
|
814
|
+
console.error(text, e);
|
|
807
815
|
}
|
|
808
816
|
}
|
|
809
817
|
static #formatError(value) {
|
|
@@ -812,10 +820,10 @@ class AppLayout {
|
|
|
812
820
|
return String(value);
|
|
813
821
|
}
|
|
814
822
|
constructor() {
|
|
815
|
-
require(
|
|
823
|
+
require("../../modules/chui_fonts").install();
|
|
816
824
|
// Глобальные стили
|
|
817
|
-
chui_functions.setStyles(__dirname + `/global_style.css`,
|
|
818
|
-
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");
|
|
819
827
|
// Необработанные ошибки и reject’ы renderer-процесса — один раз на процесс
|
|
820
828
|
if (app_error_handlers_installed === false) {
|
|
821
829
|
app_error_handlers_installed = true;
|
|
@@ -825,11 +833,11 @@ class AppLayout {
|
|
|
825
833
|
//if (process.platform !== "darwin") document.body.style.border = "1px solid var(--border_color)";
|
|
826
834
|
// ===
|
|
827
835
|
|
|
828
|
-
document.body.appendChild(this.#header.set())
|
|
829
|
-
document.body.appendChild(this.#center)
|
|
830
|
-
document.body.appendChild(this.#appMenu.getMenu())
|
|
831
|
-
document.body.appendChild(this.#notificationBox.getBox())
|
|
832
|
-
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);
|
|
833
841
|
// Части layout'а зарегистрированы: уведомления и Route ищут свои узлы по ролям, а не по id
|
|
834
842
|
this.#layout.set(LAYOUT_ROLE.NOTIFICATION_PANEL, this.#notificationPanel);
|
|
835
843
|
registerLayout(this.#layout);
|
|
@@ -844,41 +852,43 @@ class AppLayout {
|
|
|
844
852
|
ipcRenderer.on("sendNotificationDownload", (e, text, body) => {
|
|
845
853
|
this.#downloadNotification = new DownloadNotification({ title: text, text: body });
|
|
846
854
|
this.#downloadNotification.show();
|
|
847
|
-
})
|
|
855
|
+
});
|
|
848
856
|
ipcRenderer.on("sendNotificationDownloadUpdate", (e, title, text) => {
|
|
849
857
|
if (this.#downloadNotification !== undefined) this.#downloadNotification.update(title, text);
|
|
850
|
-
})
|
|
858
|
+
});
|
|
851
859
|
ipcRenderer.on("sendNotificationDownloadComplete", () => {
|
|
852
860
|
if (this.#downloadNotification !== undefined) this.#downloadNotification.done();
|
|
853
|
-
})
|
|
861
|
+
});
|
|
854
862
|
ipcRenderer.on("sendNotificationDownloadError", () => {
|
|
855
863
|
if (this.#downloadNotification !== undefined) this.#downloadNotification.error();
|
|
856
|
-
})
|
|
864
|
+
});
|
|
857
865
|
//
|
|
858
866
|
ipcRenderer.on("sendNotificationUpdateLoad", (e, text, body) => {
|
|
859
867
|
this.#updateNotificationLoad = new UpdateNotification({ title: text, text: body, spinner: true });
|
|
860
868
|
this.#updateNotificationLoad.show(true);
|
|
861
|
-
})
|
|
869
|
+
});
|
|
862
870
|
ipcRenderer.on("sendNotificationUpdateLoadClose", () => {
|
|
863
871
|
if (this.#updateNotificationLoad !== undefined) this.#updateNotificationLoad.hide();
|
|
864
|
-
})
|
|
872
|
+
});
|
|
865
873
|
ipcRenderer.on("sendNotificationUpdate", (e, text, body) => {
|
|
866
874
|
this.#updateNotification = new UpdateNotification({ title: text, text: body, spinner: false });
|
|
867
875
|
this.#updateNotification.show(true);
|
|
868
|
-
})
|
|
876
|
+
});
|
|
869
877
|
ipcRenderer.on("sendNotificationUpdateClose", () => {
|
|
870
878
|
if (this.#updateNotification !== undefined) this.#updateNotification.hide();
|
|
871
|
-
})
|
|
879
|
+
});
|
|
872
880
|
let popup = new Popup();
|
|
873
881
|
ipcRenderer.once("checkUpdatesTrue", async (e, check, version) => {
|
|
874
882
|
if (check) {
|
|
875
883
|
let confirm_res = await popup.confirm({
|
|
876
|
-
title: I18n.t("appLayout.update.available", {version: version}),
|
|
877
|
-
|
|
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"),
|
|
878
888
|
});
|
|
879
|
-
e.sender.send("updateInstallConfirm", confirm_res)
|
|
889
|
+
e.sender.send("updateInstallConfirm", confirm_res);
|
|
880
890
|
}
|
|
881
|
-
})
|
|
891
|
+
});
|
|
882
892
|
}
|
|
883
893
|
setWindowControlsLeft() {
|
|
884
894
|
this.#header.addWC(true);
|
|
@@ -905,64 +915,84 @@ class AppLayout {
|
|
|
905
915
|
this.#header.addToLeftBeforeTitle(...headerLeft);
|
|
906
916
|
}
|
|
907
917
|
disableAppMenu() {
|
|
908
|
-
this.#layout.get(LAYOUT_ROLE.APP_MENU_BUTTON).remove()
|
|
918
|
+
this.#layout.get(LAYOUT_ROLE.APP_MENU_BUTTON).remove();
|
|
909
919
|
// Отступ имени страницы считает CSS: `.page_name_no_menu` в global_style.css
|
|
910
|
-
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");
|
|
911
921
|
}
|
|
912
922
|
setScript(pathToJS = String(), id = String()) {
|
|
913
|
-
let data = fs.readFileSync(pathToJS,
|
|
914
|
-
let script = document.createElement(
|
|
923
|
+
let data = fs.readFileSync(pathToJS, "utf8");
|
|
924
|
+
let script = document.createElement("script");
|
|
915
925
|
script.innerHTML = String(data);
|
|
916
|
-
script.setAttribute(
|
|
926
|
+
script.setAttribute("id", id);
|
|
917
927
|
// Скрипт подключается в сам документ (не узел компонента), поэтому проверка по id и document
|
|
918
928
|
if (document.querySelector(`script[id="${id}"]`) === null) document.body.appendChild(script);
|
|
919
929
|
}
|
|
920
|
-
static TABS(
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
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 = {
|
|
941
|
+
title: String(),
|
|
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 = {
|
|
947
960
|
title: String(),
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
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: [],
|
|
956
985
|
},
|
|
957
|
-
components: []
|
|
958
986
|
}
|
|
959
|
-
|
|
987
|
+
) {
|
|
988
|
+
return new HeaderDialog(options).set();
|
|
989
|
+
}
|
|
960
990
|
}
|
|
961
991
|
|
|
962
992
|
class HeaderTabs {
|
|
963
993
|
#id_list = require("randomstring").generate();
|
|
964
994
|
#header_tabs_list = undefined;
|
|
965
|
-
#header_tabs = document.createElement(
|
|
995
|
+
#header_tabs = document.createElement("header_tabs");
|
|
966
996
|
#keydown_event = (event) => this.#onKeydown(event);
|
|
967
997
|
constructor(options = { width: String(), default: Number(), tabs: [] }) {
|
|
968
998
|
this.#header_tabs_list = options.tabs;
|
|
@@ -975,13 +1005,13 @@ class HeaderTabs {
|
|
|
975
1005
|
item.setAttribute("role", "tab");
|
|
976
1006
|
item.setAttribute("tabindex", "-1");
|
|
977
1007
|
this.#header_tabs.appendChild(item);
|
|
978
|
-
item.addEventListener(
|
|
1008
|
+
item.addEventListener("click", (event) => {
|
|
979
1009
|
if (event.target.tagName === "HEADER_BUTTON_ICON" || event.target.tagName === "HEADER_BUTTON_TITLE") {
|
|
980
|
-
this.#setActive(event.target.parentNode)
|
|
1010
|
+
this.#setActive(event.target.parentNode);
|
|
981
1011
|
} else {
|
|
982
|
-
this.#setActive(event.target)
|
|
1012
|
+
this.#setActive(event.target);
|
|
983
1013
|
}
|
|
984
|
-
})
|
|
1014
|
+
});
|
|
985
1015
|
}
|
|
986
1016
|
// Клик по выбранной вкладке не переигрывает её действие — как у пункта меню текущего
|
|
987
1017
|
// раздела (`route_active`, который тоже не переходит повторно). Раньше клик до вкладки
|
|
@@ -989,11 +1019,15 @@ class HeaderTabs {
|
|
|
989
1019
|
// кликом он снимал у неё и курсор-указатель, и ховер, которых у соседних кнопок шапки
|
|
990
1020
|
// нет. Слушатель ловит клик в фазе перехвата: действие вкладки и `#setActive` висят
|
|
991
1021
|
// на самом элементе и иначе сработали бы первыми
|
|
992
|
-
this.#header_tabs.addEventListener(
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
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
|
+
);
|
|
997
1031
|
// Пока ни одна вкладка не выбрана, остановкой обхода становится первая по счёту:
|
|
998
1032
|
// иначе в полосу нельзя было бы войти с клавиатуры вообще
|
|
999
1033
|
const default_tab = this.#header_tabs_list[options.default === undefined ? 0 : options.default];
|
|
@@ -1003,9 +1037,9 @@ class HeaderTabs {
|
|
|
1003
1037
|
for (let item of this.#header_tabs_list) {
|
|
1004
1038
|
if (this.#header_tabs_list.indexOf(item) === options.default) {
|
|
1005
1039
|
setTimeout(() => {
|
|
1006
|
-
item.click()
|
|
1040
|
+
item.click();
|
|
1007
1041
|
//this.#setActive(item)
|
|
1008
|
-
}, 250)
|
|
1042
|
+
}, 250);
|
|
1009
1043
|
}
|
|
1010
1044
|
}
|
|
1011
1045
|
}
|
|
@@ -1016,13 +1050,13 @@ class HeaderTabs {
|
|
|
1016
1050
|
if (target.getAttribute("active") === null) {
|
|
1017
1051
|
// `children`, а не `childNodes`: в `childNodes` попадают и текстовые узлы, у которых нет classList.
|
|
1018
1052
|
// `children` — HTMLCollection, поэтому перебираем копию
|
|
1019
|
-
[...this.#header_tabs.children].forEach(child => {
|
|
1053
|
+
[...this.#header_tabs.children].forEach((child) => {
|
|
1020
1054
|
child.removeAttribute("active");
|
|
1021
1055
|
child.removeAttribute("aria-current");
|
|
1022
1056
|
child.removeAttribute("aria-selected");
|
|
1023
1057
|
child.setAttribute("tabindex", "-1");
|
|
1024
|
-
child.classList.remove("header_tab_active")
|
|
1025
|
-
})
|
|
1058
|
+
child.classList.remove("header_tab_active");
|
|
1059
|
+
});
|
|
1026
1060
|
target.setAttribute("active", true);
|
|
1027
1061
|
// Вкладка без `active` была только подсвечена классом: скринридер о выборе не знал (как у пункта меню маршрутов)
|
|
1028
1062
|
target.setAttribute("aria-current", "page");
|
|
@@ -1030,7 +1064,7 @@ class HeaderTabs {
|
|
|
1030
1064
|
target.setAttribute("aria-selected", "true");
|
|
1031
1065
|
// Выбранная вкладка — единственная остановка Tab в полосе вкладок
|
|
1032
1066
|
target.setAttribute("tabindex", "0");
|
|
1033
|
-
target.classList.add("header_tab_active")
|
|
1067
|
+
target.classList.add("header_tab_active");
|
|
1034
1068
|
}
|
|
1035
1069
|
}
|
|
1036
1070
|
/*
|
|
@@ -1047,7 +1081,7 @@ class HeaderTabs {
|
|
|
1047
1081
|
if (tabs.length === 0) return;
|
|
1048
1082
|
const focused = tabs.indexOf(document.activeElement);
|
|
1049
1083
|
// Фокус мог быть не в полосе: тогда считаем от вкладки с остановкой обхода
|
|
1050
|
-
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;
|
|
1051
1085
|
const start = from === -1 ? 0 : from;
|
|
1052
1086
|
const last = tabs.length - 1;
|
|
1053
1087
|
let next;
|
|
@@ -1082,23 +1116,25 @@ class UserProfile {
|
|
|
1082
1116
|
#window_click_event = (event) => {
|
|
1083
1117
|
if (this.#user_main.contains(event.target) || this.#user_dropdown.contains(event.target)) return;
|
|
1084
1118
|
this.#closeMenu();
|
|
1085
|
-
}
|
|
1119
|
+
};
|
|
1086
1120
|
// Выбор пункта закрывает меню. Обработчик на списке, а не на каждом пункте: пункты
|
|
1087
1121
|
// приходят из UserDDItem и могут быть добавлены кем угодно снаружи
|
|
1088
1122
|
#dropdown_click_event = (event) => {
|
|
1089
1123
|
const item = event.target.closest === undefined ? null : event.target.closest("user_item");
|
|
1090
1124
|
if (item === null || !this.#user_dropdown.contains(item)) return;
|
|
1091
1125
|
this.#closeMenu();
|
|
1092
|
-
}
|
|
1126
|
+
};
|
|
1093
1127
|
//
|
|
1094
|
-
constructor(
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1128
|
+
constructor(
|
|
1129
|
+
options = {
|
|
1130
|
+
username: String(),
|
|
1131
|
+
image: { noImage: Boolean(), imageLink: String(), imageBase64: String(), path: String() },
|
|
1132
|
+
items: [],
|
|
1133
|
+
}
|
|
1134
|
+
) {
|
|
1099
1135
|
this.#username = options.username;
|
|
1100
1136
|
this.#user_button.innerText = options.username;
|
|
1101
|
-
this.#user_main.appendChild(this.#user_button)
|
|
1137
|
+
this.#user_main.appendChild(this.#user_button);
|
|
1102
1138
|
UserProfile.#instances.set(this.#user_main, this);
|
|
1103
1139
|
// Панель — слой в body: внутри шапки (у неё свой backdrop-filter) её blur не работал бы,
|
|
1104
1140
|
// а выдвижные слои оболочки перекрывали бы её. Идентификатор нужен для aria-controls
|
|
@@ -1107,7 +1143,7 @@ class UserProfile {
|
|
|
1107
1143
|
this.#user_button.addEventListener("click", () => {
|
|
1108
1144
|
if (this.#is_open) this.#closeMenu();
|
|
1109
1145
|
else this.#openMenu();
|
|
1110
|
-
})
|
|
1146
|
+
});
|
|
1111
1147
|
//
|
|
1112
1148
|
if (options.image !== undefined) {
|
|
1113
1149
|
// Имя пользователя уже есть в кнопке и в пунктах меню, поэтому аватар — украшение
|
|
@@ -1115,7 +1151,7 @@ class UserProfile {
|
|
|
1115
1151
|
name: options.username,
|
|
1116
1152
|
image: options.image,
|
|
1117
1153
|
size: "80px",
|
|
1118
|
-
decorative: true
|
|
1154
|
+
decorative: true,
|
|
1119
1155
|
});
|
|
1120
1156
|
this.#user_dd_image_main.appendChild(this.#avatar.set());
|
|
1121
1157
|
this.#user_dropdown.appendChild(this.#user_dd_image_main);
|
|
@@ -1127,7 +1163,7 @@ class UserProfile {
|
|
|
1127
1163
|
this.#menuKeyboard = new MenuKeyboard(this.#user_dropdown, {
|
|
1128
1164
|
itemSelector: "user_item",
|
|
1129
1165
|
onEscape: () => this.#closeMenu(),
|
|
1130
|
-
trigger: this.#user_button
|
|
1166
|
+
trigger: this.#user_button,
|
|
1131
1167
|
});
|
|
1132
1168
|
makeKeyboardButton(this.#user_button);
|
|
1133
1169
|
// Закрытое меню объявляется закрытым сразу: иначе у кнопки не было бы aria-expanded
|
|
@@ -1220,11 +1256,13 @@ class UserProfile {
|
|
|
1220
1256
|
class UserDDItem {
|
|
1221
1257
|
#user_item = document.createElement("user_item");
|
|
1222
1258
|
|
|
1223
|
-
constructor(
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1259
|
+
constructor(
|
|
1260
|
+
options = {
|
|
1261
|
+
title: String(),
|
|
1262
|
+
icon: undefined,
|
|
1263
|
+
clickEvent: () => {},
|
|
1264
|
+
}
|
|
1265
|
+
) {
|
|
1228
1266
|
if (options.title !== undefined && options.icon !== undefined) {
|
|
1229
1267
|
this.#user_item.innerText = options.title;
|
|
1230
1268
|
// Значок ведёт строку — как в пунктах context_menu_item и выпадающих списков
|
|
@@ -1236,7 +1274,7 @@ class UserDDItem {
|
|
|
1236
1274
|
} else if (options.icon !== undefined) {
|
|
1237
1275
|
this.#user_item.innerHTML = new Icon(options.icon).getHTML();
|
|
1238
1276
|
}
|
|
1239
|
-
this.#user_item.addEventListener("click", options.clickEvent)
|
|
1277
|
+
this.#user_item.addEventListener("click", options.clickEvent);
|
|
1240
1278
|
}
|
|
1241
1279
|
|
|
1242
1280
|
set() {
|
|
@@ -1246,25 +1284,27 @@ class UserDDItem {
|
|
|
1246
1284
|
|
|
1247
1285
|
class HeaderDialog {
|
|
1248
1286
|
#header_button = undefined;
|
|
1249
|
-
constructor(
|
|
1250
|
-
|
|
1251
|
-
icon: undefined,
|
|
1252
|
-
reverse: Boolean(),
|
|
1253
|
-
label: String(),
|
|
1254
|
-
dialogOptions: {
|
|
1287
|
+
constructor(
|
|
1288
|
+
options = {
|
|
1255
1289
|
title: String(),
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
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: [],
|
|
1264
1305
|
},
|
|
1265
|
-
components: []
|
|
1266
1306
|
}
|
|
1267
|
-
|
|
1307
|
+
) {
|
|
1268
1308
|
// Неполные `dialogOptions` сборку не роняют: `style` и `components` — необязательные
|
|
1269
1309
|
// части опции, а прямое обращение к ним давало TypeError при создании кнопки
|
|
1270
1310
|
// («Cannot read properties of undefined») ещё до первого открытия слоя
|
|
@@ -1272,22 +1312,22 @@ class HeaderDialog {
|
|
|
1272
1312
|
const dialog_style = dialog_options.style === undefined ? {} : dialog_options.style;
|
|
1273
1313
|
const dialog_components = dialog_options.components === undefined ? [] : dialog_options.components;
|
|
1274
1314
|
// Пустой заголовок остаётся пустым: заготовка опции рисовала бы слово «undefined»
|
|
1275
|
-
const dialog_title =
|
|
1276
|
-
? "" : dialog_options.title;
|
|
1315
|
+
const dialog_title =
|
|
1316
|
+
dialog_options.title === undefined || dialog_options.title === null ? "" : dialog_options.title;
|
|
1277
1317
|
let dialog = new Dialog({
|
|
1278
1318
|
width: dialog_style.width,
|
|
1279
1319
|
height: dialog_style.height,
|
|
1280
1320
|
closeOutSideClick: dialog_options.closeOutSideClick,
|
|
1281
1321
|
// Имя модального слоя для скринридера: без него диалог объявлялся бы безымянным
|
|
1282
|
-
title: dialog_title
|
|
1283
|
-
})
|
|
1322
|
+
title: dialog_title,
|
|
1323
|
+
});
|
|
1284
1324
|
// HEADER
|
|
1285
1325
|
let header_dialog = new ContentBlock({
|
|
1286
1326
|
direction: "row",
|
|
1287
1327
|
wrap: "nowrap",
|
|
1288
1328
|
align: "center",
|
|
1289
1329
|
justify: "space-between",
|
|
1290
|
-
disableMarginChild: true
|
|
1330
|
+
disableMarginChild: true,
|
|
1291
1331
|
});
|
|
1292
1332
|
header_dialog.setWidth("-webkit-fill-available");
|
|
1293
1333
|
// Заголовок — как у заголовков панелей оболочки (размер `--font_default_size`, вес 500
|
|
@@ -1295,13 +1335,13 @@ class HeaderDialog {
|
|
|
1295
1335
|
let title = new Label({
|
|
1296
1336
|
markdownText: `**${dialog_title}**`,
|
|
1297
1337
|
wordBreak: "normal",
|
|
1298
|
-
width: "max-content"
|
|
1299
|
-
})
|
|
1338
|
+
width: "max-content",
|
|
1339
|
+
});
|
|
1300
1340
|
let close_button = new Button({
|
|
1301
1341
|
icon: Icons.NAVIGATION.CLOSE,
|
|
1302
1342
|
label: I18n.t("common.close"),
|
|
1303
1343
|
reverse: true,
|
|
1304
|
-
clickEvent: () => dialog.close()
|
|
1344
|
+
clickEvent: () => dialog.close(),
|
|
1305
1345
|
});
|
|
1306
1346
|
header_dialog.add(title, close_button);
|
|
1307
1347
|
dialog.addToHeader(header_dialog);
|
|
@@ -1310,10 +1350,10 @@ class HeaderDialog {
|
|
|
1310
1350
|
direction: dialog_style.direction,
|
|
1311
1351
|
wrap: dialog_style.wrap,
|
|
1312
1352
|
align: dialog_style.align,
|
|
1313
|
-
justify: dialog_style.justify
|
|
1353
|
+
justify: dialog_style.justify,
|
|
1314
1354
|
});
|
|
1315
1355
|
body_dialog.setWidth("-webkit-fill-available");
|
|
1316
|
-
body_dialog.add(...dialog_components)
|
|
1356
|
+
body_dialog.add(...dialog_components);
|
|
1317
1357
|
dialog.addToBody(body_dialog);
|
|
1318
1358
|
// FOOTER
|
|
1319
1359
|
|
|
@@ -1335,14 +1375,15 @@ class HeaderDialog {
|
|
|
1335
1375
|
class HeaderButton {
|
|
1336
1376
|
#header_button = undefined;
|
|
1337
1377
|
|
|
1338
|
-
constructor(
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1378
|
+
constructor(
|
|
1379
|
+
options = {
|
|
1380
|
+
title: String(),
|
|
1381
|
+
icon: undefined,
|
|
1382
|
+
reverse: Boolean(),
|
|
1383
|
+
label: String(),
|
|
1384
|
+
clickEvent: () => {},
|
|
1344
1385
|
}
|
|
1345
|
-
|
|
1386
|
+
) {
|
|
1346
1387
|
this.#header_button = makeHeaderButtonBox(options);
|
|
1347
1388
|
this.#header_button.addEventListener("click", options.clickEvent);
|
|
1348
1389
|
}
|
|
@@ -1352,5 +1393,5 @@ class HeaderButton {
|
|
|
1352
1393
|
}
|
|
1353
1394
|
}
|
|
1354
1395
|
|
|
1355
|
-
exports.AppLayout = AppLayout
|
|
1356
|
-
exports.Route = Route
|
|
1396
|
+
exports.AppLayout = AppLayout;
|
|
1397
|
+
exports.Route = Route;
|