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.
Files changed (221) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +43 -644
  3. package/framework/appLayout/default/chui_app_layout.js +308 -260
  4. package/framework/appLayout/default/global_style.css +85 -48
  5. package/framework/components/chui_accordion/accordion.js +29 -26
  6. package/framework/components/chui_accordion/styles.css +6 -5
  7. package/framework/components/chui_action_sheet/action_sheet.js +59 -33
  8. package/framework/components/chui_action_sheet/styles.css +2 -1
  9. package/framework/components/chui_affix/affix.js +11 -6
  10. package/framework/components/chui_alert/alert.js +23 -14
  11. package/framework/components/chui_avatar/avatar.js +23 -22
  12. package/framework/components/chui_avatar_group/avatar_group.js +21 -19
  13. package/framework/components/chui_backtop/backtop.js +35 -19
  14. package/framework/components/chui_badge/badge.js +15 -13
  15. package/framework/components/chui_breadcrumb/breadcrumb.js +15 -13
  16. package/framework/components/chui_button/button.js +42 -22
  17. package/framework/components/chui_button/styles.css +2 -2
  18. package/framework/components/chui_calendar/calendar.js +12 -10
  19. package/framework/components/chui_card/card.js +43 -23
  20. package/framework/components/chui_chat/chat.js +72 -33
  21. package/framework/components/chui_clipboard_history/clipboard_history.js +30 -21
  22. package/framework/components/chui_clipboard_history/styles.css +4 -2
  23. package/framework/components/chui_code/code.js +2 -2
  24. package/framework/components/chui_code/styles.css +2 -2
  25. package/framework/components/chui_code_editor/code_editor.js +47 -31
  26. package/framework/components/chui_code_editor/styles.css +3 -2
  27. package/framework/components/chui_command_palette/command_palette.js +60 -37
  28. package/framework/components/chui_console/console.js +5 -5
  29. package/framework/components/chui_console/styles.css +1 -1
  30. package/framework/components/chui_content_block/content_block.js +33 -19
  31. package/framework/components/chui_content_block/styles.css +2 -2
  32. package/framework/components/chui_context_menu/context_menu.js +9 -4
  33. package/framework/components/chui_custom_element/custom_element.js +22 -15
  34. package/framework/components/chui_data_grid/data_grid.js +396 -234
  35. package/framework/components/chui_data_grid/styles.css +10 -8
  36. package/framework/components/chui_descriptions/descriptions.js +34 -17
  37. package/framework/components/chui_details/details.js +32 -26
  38. package/framework/components/chui_details/styles.css +5 -4
  39. package/framework/components/chui_diff_viewer/diff_viewer.js +91 -66
  40. package/framework/components/chui_diff_viewer/styles.css +1 -1
  41. package/framework/components/chui_divider/divider.js +13 -11
  42. package/framework/components/chui_drawer/drawer.js +27 -25
  43. package/framework/components/chui_drop_zone/drop_zone.js +31 -21
  44. package/framework/components/chui_empty_state/empty_state.js +20 -15
  45. package/framework/components/chui_fieldset/fieldset.js +37 -20
  46. package/framework/components/chui_fieldset/styles.css +1 -1
  47. package/framework/components/chui_file_explorer/file_explorer.js +157 -86
  48. package/framework/components/chui_file_explorer/styles.css +2 -1
  49. package/framework/components/chui_form/form.js +31 -19
  50. package/framework/components/chui_form/styles.css +1 -1
  51. package/framework/components/chui_gantt/gantt.js +64 -40
  52. package/framework/components/chui_graphs/graphs.js +301 -200
  53. package/framework/components/chui_graphs/styles.css +1 -1
  54. package/framework/components/chui_hero/hero.js +40 -22
  55. package/framework/components/chui_html_block/html_block.js +6 -6
  56. package/framework/components/chui_html_block/styles.css +2 -2
  57. package/framework/components/chui_hx/hx.js +3 -3
  58. package/framework/components/chui_hx/styles.css +11 -4
  59. package/framework/components/chui_icons/icons.js +2109 -2109
  60. package/framework/components/chui_icons/styles.css +2 -2
  61. package/framework/components/chui_inputs/chui_check_box/check_box.js +31 -19
  62. package/framework/components/chui_inputs/chui_check_box/styles.css +18 -18
  63. package/framework/components/chui_inputs/chui_chips/chips.js +50 -40
  64. package/framework/components/chui_inputs/chui_chips/styles.css +4 -2
  65. package/framework/components/chui_inputs/chui_color_picker/color_picker.js +36 -24
  66. package/framework/components/chui_inputs/chui_combo_box/combo_box.js +100 -82
  67. package/framework/components/chui_inputs/chui_combo_box/styles.css +14 -12
  68. package/framework/components/chui_inputs/chui_date/date.js +102 -66
  69. package/framework/components/chui_inputs/chui_date/styles.css +12 -11
  70. package/framework/components/chui_inputs/chui_date_range/date_range.js +40 -29
  71. package/framework/components/chui_inputs/chui_datetime/datetime.js +35 -27
  72. package/framework/components/chui_inputs/chui_email/email.js +43 -31
  73. package/framework/components/chui_inputs/chui_email/styles.css +5 -5
  74. package/framework/components/chui_inputs/chui_file/file.js +68 -41
  75. package/framework/components/chui_inputs/chui_file/styles.css +13 -12
  76. package/framework/components/chui_inputs/chui_hotkey/hotkey.js +61 -36
  77. package/framework/components/chui_inputs/chui_masked/masked.js +39 -28
  78. package/framework/components/chui_inputs/chui_mention/mention.js +77 -30
  79. package/framework/components/chui_inputs/chui_multi_combo_box/multi_combo_box.js +173 -153
  80. package/framework/components/chui_inputs/chui_multi_combo_box/styles.css +9 -7
  81. package/framework/components/chui_inputs/chui_number/number.js +58 -41
  82. package/framework/components/chui_inputs/chui_number/styles.css +7 -7
  83. package/framework/components/chui_inputs/chui_otp/otp.js +38 -29
  84. package/framework/components/chui_inputs/chui_password/password.js +58 -36
  85. package/framework/components/chui_inputs/chui_password/styles.css +8 -8
  86. package/framework/components/chui_inputs/chui_radio_button/radio_button.js +37 -25
  87. package/framework/components/chui_inputs/chui_radio_button/styles.css +20 -20
  88. package/framework/components/chui_inputs/chui_rating/rating.js +26 -22
  89. package/framework/components/chui_inputs/chui_select_box/select_box.js +102 -78
  90. package/framework/components/chui_inputs/chui_select_box/styles.css +11 -11
  91. package/framework/components/chui_inputs/chui_slider/slider.js +29 -26
  92. package/framework/components/chui_inputs/chui_slider/styles.css +2 -1
  93. package/framework/components/chui_inputs/chui_text/styles.css +8 -8
  94. package/framework/components/chui_inputs/chui_text/text.js +65 -44
  95. package/framework/components/chui_inputs/chui_text_area/styles.css +6 -6
  96. package/framework/components/chui_inputs/chui_text_area/text_area.js +51 -28
  97. package/framework/components/chui_inputs/chui_time/styles.css +5 -3
  98. package/framework/components/chui_inputs/chui_time/time.js +38 -27
  99. package/framework/components/chui_inputs/chui_toggle/styles.css +3 -3
  100. package/framework/components/chui_inputs/chui_toggle/toggle.js +16 -16
  101. package/framework/components/chui_json_editor/json_editor.js +53 -43
  102. package/framework/components/chui_json_viewer/json_viewer.js +52 -33
  103. package/framework/components/chui_json_viewer/styles.css +3 -2
  104. package/framework/components/chui_kanban/kanban.js +52 -34
  105. package/framework/components/chui_kbd/kbd.js +12 -10
  106. package/framework/components/chui_label/label.js +14 -12
  107. package/framework/components/chui_label/styles.css +2 -2
  108. package/framework/components/chui_link/link.js +27 -20
  109. package/framework/components/chui_list/list.js +31 -27
  110. package/framework/components/chui_list/styles.css +2 -1
  111. package/framework/components/chui_markdown_editor/markdown_editor.js +60 -44
  112. package/framework/components/chui_media/audio.js +326 -227
  113. package/framework/components/chui_media/audio_styles.css +40 -21
  114. package/framework/components/chui_media/image.js +70 -38
  115. package/framework/components/chui_media/image_compare.js +52 -21
  116. package/framework/components/chui_media/image_styles.css +3 -3
  117. package/framework/components/chui_media/video.js +155 -62
  118. package/framework/components/chui_media/video_styles.css +33 -16
  119. package/framework/components/chui_menu_bar/menu_bar.js +26 -24
  120. package/framework/components/chui_menu_bar/styles.css +4 -2
  121. package/framework/components/chui_modal/modal.js +44 -43
  122. package/framework/components/chui_modal/styles.css +3 -2
  123. package/framework/components/chui_notification/notification.js +50 -34
  124. package/framework/components/chui_notification/notification_download.js +31 -25
  125. package/framework/components/chui_notification/notification_download_progress.css +3 -2
  126. package/framework/components/chui_notification/notification_download_progress.js +47 -41
  127. package/framework/components/chui_notification/notification_update.js +23 -16
  128. package/framework/components/chui_notification/status_plate.js +2 -2
  129. package/framework/components/chui_notification/styles_download.css +1 -1
  130. package/framework/components/chui_notification/styles_update.css +1 -1
  131. package/framework/components/chui_org_chart/org_chart.js +74 -35
  132. package/framework/components/chui_pagination/pagination.js +62 -27
  133. package/framework/components/chui_paragraph/paragraph.js +2 -2
  134. package/framework/components/chui_paragraph/styles.css +1 -1
  135. package/framework/components/chui_popover/popover.js +49 -28
  136. package/framework/components/chui_popups/popups.js +137 -108
  137. package/framework/components/chui_popups/styles.css +2 -2
  138. package/framework/components/chui_print_button/print_button.js +33 -23
  139. package/framework/components/chui_progress_bar/progress_bar.js +23 -15
  140. package/framework/components/chui_progress_bar/styles.css +2 -2
  141. package/framework/components/chui_progress_ring/progress_ring.js +17 -15
  142. package/framework/components/chui_qrcode/qrcode.js +75 -31
  143. package/framework/components/chui_query_builder/query_builder.js +216 -123
  144. package/framework/components/chui_radio_group/radio_group.js +36 -26
  145. package/framework/components/chui_radio_group/styles.css +1 -1
  146. package/framework/components/chui_result/result.js +26 -16
  147. package/framework/components/chui_scheduler/scheduler.js +57 -35
  148. package/framework/components/chui_scheduler/styles.css +6 -2
  149. package/framework/components/chui_segmented_control/segmented_control.js +27 -21
  150. package/framework/components/chui_settings/settings.js +122 -48
  151. package/framework/components/chui_shortcut_help/shortcut_help.js +55 -32
  152. package/framework/components/chui_sidebar_nav/sidebar_nav.js +33 -23
  153. package/framework/components/chui_skeleton/skeleton.js +29 -24
  154. package/framework/components/chui_slideshow/slideshow.js +60 -41
  155. package/framework/components/chui_slideshow/styles.css +18 -12
  156. package/framework/components/chui_spinner/spinner.js +8 -8
  157. package/framework/components/chui_spinner/styles.css +0 -1
  158. package/framework/components/chui_split_button/split_button.js +23 -18
  159. package/framework/components/chui_split_button/styles.css +1 -1
  160. package/framework/components/chui_splitter/splitter.js +35 -28
  161. package/framework/components/chui_statistic/statistic.js +65 -38
  162. package/framework/components/chui_status_bar/status_bar.js +11 -9
  163. package/framework/components/chui_stepper/stepper.js +27 -22
  164. package/framework/components/chui_sticky_bar/sticky_bar.js +9 -4
  165. package/framework/components/chui_sticky_bar/styles.css +3 -1
  166. package/framework/components/chui_tabs/chui_tabs.js +28 -22
  167. package/framework/components/chui_tabs/styles.css +3 -3
  168. package/framework/components/chui_text_editor/styles.css +20 -19
  169. package/framework/components/chui_text_editor/text_editor.js +85 -61
  170. package/framework/components/chui_text_editor/text_editor_panel.js +346 -284
  171. package/framework/components/chui_theme_toggle/theme_toggle.js +27 -22
  172. package/framework/components/chui_timeline/timeline.js +14 -12
  173. package/framework/components/chui_toast_queue/toast_queue.js +52 -46
  174. package/framework/components/chui_toolbar/toolbar.js +20 -13
  175. package/framework/components/chui_tooltip/tooltip.js +37 -24
  176. package/framework/components/chui_tour/styles.css +8 -3
  177. package/framework/components/chui_tour/tour.js +61 -27
  178. package/framework/components/chui_transfer/styles.css +2 -1
  179. package/framework/components/chui_transfer/transfer.js +75 -42
  180. package/framework/components/chui_tree_table/tree_table.js +43 -29
  181. package/framework/components/chui_tree_view/styles.css +4 -2
  182. package/framework/components/chui_tree_view/tree_view.js +142 -99
  183. package/framework/components/chui_watermark/watermark.js +24 -16
  184. package/framework/components/chui_webview/styles.css +2 -2
  185. package/framework/components/chui_webview/webview.js +62 -50
  186. package/framework/components/telegram_bot/chui_telegram_bot.js +71 -64
  187. package/framework/modules/chui_a11y/chui_a11y.js +77 -46
  188. package/framework/modules/chui_animations/animations.js +42 -42
  189. package/framework/modules/chui_animations/styles.css +1 -1
  190. package/framework/modules/chui_bridge/preload.js +36 -25
  191. package/framework/modules/chui_functions.js +52 -50
  192. package/framework/modules/chui_highlight/chui_highlight.js +25 -22
  193. package/framework/modules/chui_i18n/chui_i18n.js +9 -7
  194. package/framework/modules/chui_i18n/locales/a11y.js +5 -5
  195. package/framework/modules/chui_i18n/locales/app_layout.js +5 -5
  196. package/framework/modules/chui_i18n/locales/calendar.js +3 -3
  197. package/framework/modules/chui_i18n/locales/common.js +3 -3
  198. package/framework/modules/chui_i18n/locales/data.js +36 -26
  199. package/framework/modules/chui_i18n/locales/editors.js +40 -13
  200. package/framework/modules/chui_i18n/locales/feedback.js +3 -3
  201. package/framework/modules/chui_i18n/locales/forms.js +8 -8
  202. package/framework/modules/chui_i18n/locales/graphics.js +8 -8
  203. package/framework/modules/chui_i18n/locales/media.js +4 -4
  204. package/framework/modules/chui_i18n/locales/navigation.js +8 -8
  205. package/framework/modules/chui_i18n/locales/validate.js +3 -3
  206. package/framework/modules/chui_json/chui_json.js +9 -9
  207. package/framework/modules/chui_layout/chui_layout.js +12 -10
  208. package/framework/modules/chui_logger/chui_logger.js +29 -25
  209. package/framework/modules/chui_page/page.js +33 -27
  210. package/framework/modules/chui_qr/chui_qr.js +158 -67
  211. package/framework/modules/chui_scrollbar/chui_scrollbar.js +35 -24
  212. package/framework/modules/chui_scrollbar/styles.css +3 -1
  213. package/framework/modules/chui_services/chui_channels.js +27 -13
  214. package/framework/modules/chui_services/chui_services.js +39 -31
  215. package/framework/modules/chui_services/desktop.js +32 -31
  216. package/framework/modules/chui_sortable/chui_sortable.js +17 -12
  217. package/framework/modules/chui_validate/chui_validate.js +7 -4
  218. package/framework/tray_bar/chui_menu_items.js +136 -46
  219. package/index.d.ts +4814 -0
  220. package/index.js +385 -292
  221. package/package.json +79 -68
@@ -1,24 +1,24 @@
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");
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('../../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');
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('page');
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: '<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>',
165
- maximize: '<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>',
166
- restore: '<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>',
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) this.#header_left_box.insertBefore(component, this.#header_left_box.firstChild);
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) this.#header_right_box.insertBefore(component, this.#header_right_box.firstChild);
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('center');
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('app_menu');
364
+ #appMenu = document.createElement("app_menu");
360
365
  #appMenuWidth = 400;
361
366
  #appMenuOffset = 25;
362
- get #appMenuSlide() { return this.#appMenuWidth + this.#appMenuOffset; }
363
- #routeViews = document.createElement('route_views');
364
- #appMenuButton = document.createElement('app_menu_button');
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('page_name');
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('click', (e) => {
409
+ });
410
+ header.set().addEventListener("click", (e) => {
404
411
  if (e.target !== this.#appMenuButton) this.#close();
405
- })
406
- center.addEventListener('click', (e) => {
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('page');
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('route');
516
- let icon_menu = document.createElement('route_icon');
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('route_title');
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('click', () => {
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('route_active')) {
536
- for (const act of this.#routes()) act.classList.remove('route_active');
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('notification_box');
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('notification_button');
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 = 'chui_notification_box';
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('click', (e) => {
659
+ header.set().addEventListener("click", (e) => {
653
660
  if (e.target !== this.#notification_button) this.#closePanel();
654
- })
655
- center.addEventListener('click', (e) => {
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)`) this.#closePanel();
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)) group.innerText = I18n.t("appLayout.notifications.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 = unread === 0
752
- ? I18n.t("appLayout.notifications.title")
753
- : I18n.t("appLayout.notifications.unreadCount", {count: unread});
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('notification_panel');
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(I18n.t("appLayout.error.uncaught"), event.error === undefined ? event.message : event.error, place)
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('../../modules/chui_fonts').install();
823
+ require("../../modules/chui_fonts").install();
809
824
  // Глобальные стили
810
- chui_functions.setStyles(__dirname + `/global_style.css`, 'chUiJS_Global_App')
811
- chui_functions.setStyles(__dirname + `/main_theme_style.css`, 'chUiJS_Main_Theme')
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}), message: I18n.t("appLayout.update.question"),
870
- cancelText: I18n.t("appLayout.update.cancel"), okText: I18n.t("appLayout.update.install")
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, 'utf8');
907
- let script = document.createElement('script');
923
+ let data = fs.readFileSync(pathToJS, "utf8");
924
+ let script = document.createElement("script");
908
925
  script.innerHTML = String(data);
909
- script.setAttribute('id', id);
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(options = {
914
- width: String(),
915
- default: Number(),
916
- tabs: []
917
- }) { return new HeaderTabs(options).set(); }
918
- static BUTTON(options = {
919
- title: String(),
920
- icon: undefined,
921
- reverse: Boolean(),
922
- clickEvent: () => {}
923
- }) { return new HeaderButton(options).set(); }
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
- closeOutSideClick: Boolean(),
942
- style: {
943
- width: String(),
944
- height: String(),
945
- direction: String(),
946
- wrap: String(),
947
- align: String(),
948
- justify: 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 = {
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
- }) { return new HeaderDialog(options).set(); }
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('header_tabs');
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('click', (event) => {
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("click", (event) => {
986
- const tab = event.target.closest === undefined ? null : event.target.closest("header_button");
987
- if (tab === null || tab.getAttribute("active") === null) return;
988
- event.stopImmediatePropagation();
989
- }, true);
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(options = {
1088
- username: String(),
1089
- image: { noImage: Boolean(), imageLink: String(), imageBase64: String(), path: String() },
1090
- items: []
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(options = {
1217
- title: String(),
1218
- icon: undefined,
1219
- clickEvent: () => {}
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(options = {
1243
- title: String(),
1244
- icon: undefined,
1245
- reverse: Boolean(),
1246
- label: String(),
1247
- dialogOptions: {
1287
+ constructor(
1288
+ options = {
1248
1289
  title: String(),
1249
- closeOutSideClick: Boolean(),
1250
- style: {
1251
- width: String(),
1252
- height: String(),
1253
- direction: String(),
1254
- wrap: String(),
1255
- align: String(),
1256
- justify: String(),
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 = dialog_options.title === undefined || dialog_options.title === null
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(options = {
1332
- title: String(),
1333
- icon: undefined,
1334
- reverse: Boolean(),
1335
- label: String(),
1336
- clickEvent: () => {
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;