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,8 +1,8 @@
|
|
|
1
|
-
const {globalShortcut, clipboard, app, dialog, nativeImage, nativeTheme, shell, BrowserWindow} = require("electron");
|
|
1
|
+
const { globalShortcut, clipboard, app, dialog, nativeImage, nativeTheme, shell, BrowserWindow } = require("electron");
|
|
2
2
|
const fs = require("fs");
|
|
3
|
-
const {Log} = require("../chui_logger/chui_logger");
|
|
4
|
-
const {SERVICES_CHANNELS} = require("./chui_channels");
|
|
5
|
-
const {I18n} = require("../chui_i18n/chui_i18n");
|
|
3
|
+
const { Log } = require("../chui_logger/chui_logger");
|
|
4
|
+
const { SERVICES_CHANNELS } = require("./chui_channels");
|
|
5
|
+
const { I18n } = require("../chui_i18n/chui_i18n");
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Десктоп-сервисы main-процесса. Имена IPC-каналов живут в `chui_channels.js` — тот модуль
|
|
@@ -173,7 +173,7 @@ class RecentDocuments {
|
|
|
173
173
|
#items = [];
|
|
174
174
|
#limit = RecentDocuments.MAX;
|
|
175
175
|
|
|
176
|
-
constructor(options = {limit: RecentDocuments.MAX}) {
|
|
176
|
+
constructor(options = { limit: RecentDocuments.MAX }) {
|
|
177
177
|
if (options.limit !== undefined) this.#limit = options.limit;
|
|
178
178
|
}
|
|
179
179
|
|
|
@@ -183,8 +183,10 @@ class RecentDocuments {
|
|
|
183
183
|
}
|
|
184
184
|
app.addRecentDocument(path);
|
|
185
185
|
// Один и тот же файл не должен появляться дважды
|
|
186
|
-
this.#items = [
|
|
187
|
-
|
|
186
|
+
this.#items = [
|
|
187
|
+
{ path: path, name: name === undefined ? path : name },
|
|
188
|
+
...this.#items.filter((item) => item.path !== path),
|
|
189
|
+
].slice(0, this.#limit);
|
|
188
190
|
return this;
|
|
189
191
|
}
|
|
190
192
|
|
|
@@ -207,7 +209,7 @@ class RecentDocuments {
|
|
|
207
209
|
return {
|
|
208
210
|
role: "recentDocuments",
|
|
209
211
|
label: pageTitle,
|
|
210
|
-
submenu: [{label: clearLabel, role: "clearRecentDocuments"}]
|
|
212
|
+
submenu: [{ label: clearLabel, role: "clearRecentDocuments" }],
|
|
211
213
|
};
|
|
212
214
|
}
|
|
213
215
|
}
|
|
@@ -223,7 +225,7 @@ class TaskbarProgress {
|
|
|
223
225
|
NORMAL: "normal",
|
|
224
226
|
ERROR: "error",
|
|
225
227
|
PAUSED: "paused",
|
|
226
|
-
INDETERMINATE: "indeterminate"
|
|
228
|
+
INDETERMINATE: "indeterminate",
|
|
227
229
|
};
|
|
228
230
|
#window = undefined;
|
|
229
231
|
#value = 0;
|
|
@@ -285,7 +287,7 @@ class TaskbarProgress {
|
|
|
285
287
|
const window = this.#window;
|
|
286
288
|
if (window === undefined || window === null || typeof window.setProgressBar !== "function") return;
|
|
287
289
|
if (window.isDestroyed()) return;
|
|
288
|
-
window.setProgressBar(value, {mode: mode});
|
|
290
|
+
window.setProgressBar(value, { mode: mode });
|
|
289
291
|
}
|
|
290
292
|
|
|
291
293
|
/**
|
|
@@ -320,7 +322,7 @@ class Theme {
|
|
|
320
322
|
static SOURCE = {
|
|
321
323
|
SYSTEM: "system",
|
|
322
324
|
LIGHT: "light",
|
|
323
|
-
DARK: "dark"
|
|
325
|
+
DARK: "dark",
|
|
324
326
|
};
|
|
325
327
|
static #KEY = "theme_source";
|
|
326
328
|
#store = undefined;
|
|
@@ -414,7 +416,7 @@ class ClipboardHistory {
|
|
|
414
416
|
#limit = ClipboardHistory.MAX;
|
|
415
417
|
#store = undefined;
|
|
416
418
|
|
|
417
|
-
constructor(options = {limit: ClipboardHistory.MAX, store: undefined}) {
|
|
419
|
+
constructor(options = { limit: ClipboardHistory.MAX, store: undefined }) {
|
|
418
420
|
if (options.limit !== undefined) this.#limit = options.limit;
|
|
419
421
|
this.#store = options.store;
|
|
420
422
|
this.restore();
|
|
@@ -477,7 +479,7 @@ class ClipboardHistory {
|
|
|
477
479
|
}
|
|
478
480
|
}
|
|
479
481
|
|
|
480
|
-
exports.Shortcuts = Shortcuts
|
|
482
|
+
exports.Shortcuts = Shortcuts;
|
|
481
483
|
/**
|
|
482
484
|
* Печать: текущее окно, произвольный HTML и PDF.
|
|
483
485
|
*
|
|
@@ -492,15 +494,18 @@ exports.Shortcuts = Shortcuts
|
|
|
492
494
|
class Print {
|
|
493
495
|
static DEFAULT_OPTIONS = {
|
|
494
496
|
printBackground: true,
|
|
495
|
-
pageSize: "A4"
|
|
497
|
+
pageSize: "A4",
|
|
496
498
|
};
|
|
497
499
|
|
|
498
500
|
/** Печать окна; возвращает `{success, failureReason}` — печать асинхронна и не бросает */
|
|
499
501
|
page(window = undefined, options = {}) {
|
|
500
|
-
if (window === undefined || window === null || window.isDestroyed())
|
|
501
|
-
|
|
502
|
+
if (window === undefined || window === null || window.isDestroyed())
|
|
503
|
+
return Promise.resolve({ success: false, failureReason: I18n.t("feedback.printNoWindow") });
|
|
504
|
+
const settings = { ...Print.DEFAULT_OPTIONS, ...options };
|
|
502
505
|
return new Promise((resolve) => {
|
|
503
|
-
window.webContents.print(settings, (success, failureReason) =>
|
|
506
|
+
window.webContents.print(settings, (success, failureReason) =>
|
|
507
|
+
resolve({ success: success, failureReason: failureReason })
|
|
508
|
+
);
|
|
504
509
|
});
|
|
505
510
|
}
|
|
506
511
|
|
|
@@ -526,19 +531,22 @@ class Print {
|
|
|
526
531
|
let file = options.path;
|
|
527
532
|
if (file === undefined) {
|
|
528
533
|
const suggested = options.fileName === undefined ? "document.pdf" : options.fileName;
|
|
529
|
-
const result = await dialog.showSaveDialog(options.parent, {
|
|
530
|
-
|
|
534
|
+
const result = await dialog.showSaveDialog(options.parent, {
|
|
535
|
+
defaultPath: suggested,
|
|
536
|
+
filters: [{ name: "PDF", extensions: ["pdf"] }],
|
|
537
|
+
});
|
|
538
|
+
if (result.canceled || result.filePath === undefined) return { saved: false, canceled: true };
|
|
531
539
|
file = result.filePath;
|
|
532
540
|
}
|
|
533
541
|
fs.writeFileSync(file, data);
|
|
534
|
-
return {saved: true, path: file, canceled: false};
|
|
542
|
+
return { saved: true, path: file, canceled: false };
|
|
535
543
|
} finally {
|
|
536
544
|
if (!window.isDestroyed()) window.close();
|
|
537
545
|
}
|
|
538
546
|
}
|
|
539
547
|
|
|
540
548
|
static #pdfOptions(options = {}) {
|
|
541
|
-
const pdf = {...Print.DEFAULT_OPTIONS};
|
|
549
|
+
const pdf = { ...Print.DEFAULT_OPTIONS };
|
|
542
550
|
for (const key of ["landscape", "pageSize", "scale", "printBackground", "margins", "preferCSSPageSize"]) {
|
|
543
551
|
if (options[key] !== undefined) pdf[key] = options[key];
|
|
544
552
|
}
|
|
@@ -550,7 +558,7 @@ class Print {
|
|
|
550
558
|
const window = new BrowserWindow({
|
|
551
559
|
show: false,
|
|
552
560
|
parent: options.parent,
|
|
553
|
-
webPreferences: {sandbox: true, contextIsolation: true, nodeIntegration: false}
|
|
561
|
+
webPreferences: { sandbox: true, contextIsolation: true, nodeIntegration: false },
|
|
554
562
|
});
|
|
555
563
|
const source = `data:text/html;charset=utf-8,${encodeURIComponent(String(html))}`;
|
|
556
564
|
await window.loadURL(source);
|
|
@@ -558,12 +566,12 @@ class Print {
|
|
|
558
566
|
}
|
|
559
567
|
}
|
|
560
568
|
|
|
561
|
-
exports.Shell = Shell
|
|
562
|
-
exports.Clipboard = Clipboard
|
|
563
|
-
exports.ClipboardHistory = ClipboardHistory
|
|
564
|
-
exports.Theme = Theme
|
|
565
|
-
exports.Locale = Locale
|
|
566
|
-
exports.RecentDocuments = RecentDocuments
|
|
567
|
-
exports.Print = Print
|
|
568
|
-
exports.TaskbarProgress = TaskbarProgress
|
|
569
|
-
exports.SERVICES_CHANNELS = SERVICES_CHANNELS
|
|
569
|
+
exports.Shell = Shell;
|
|
570
|
+
exports.Clipboard = Clipboard;
|
|
571
|
+
exports.ClipboardHistory = ClipboardHistory;
|
|
572
|
+
exports.Theme = Theme;
|
|
573
|
+
exports.Locale = Locale;
|
|
574
|
+
exports.RecentDocuments = RecentDocuments;
|
|
575
|
+
exports.Print = Print;
|
|
576
|
+
exports.TaskbarProgress = TaskbarProgress;
|
|
577
|
+
exports.SERVICES_CHANNELS = SERVICES_CHANNELS;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {SERVICES_CHANNELS, SERVICES_CALLS} = require("./chui_channels");
|
|
1
|
+
const { SERVICES_CHANNELS, SERVICES_CALLS, SERVICES_SYNC_CALLS, APP_PATH_NAMES } = require("./chui_channels");
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Десктоп-сервисы со стороны страницы: буфер обмена, последние документы, прогресс на иконке
|
|
@@ -43,25 +43,25 @@ class Desktop {
|
|
|
43
43
|
remove: (target = undefined) => Desktop.#call("clipboardHistoryRemove", target),
|
|
44
44
|
clear: () => Desktop.#call("clipboardHistoryClear"),
|
|
45
45
|
/** Подписка на изменение истории; возвращает функцию отписки */
|
|
46
|
-
onChange: (listener = () => {}) => Desktop.#subscribeHistory(listener)
|
|
47
|
-
}
|
|
46
|
+
onChange: (listener = () => {}) => Desktop.#subscribeHistory(listener),
|
|
47
|
+
},
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
static recent = {
|
|
51
51
|
add: (path = String(), name = undefined) => Desktop.#call("recentAdd", path, name),
|
|
52
52
|
clear: () => Desktop.#call("recentClear"),
|
|
53
|
-
list: () => Desktop.#call("recentList")
|
|
53
|
+
list: () => Desktop.#call("recentList"),
|
|
54
54
|
};
|
|
55
55
|
|
|
56
56
|
static progress = {
|
|
57
57
|
/** `progress.set(0.4)` — 40%; `mode` — TaskbarProgress.MODE */
|
|
58
58
|
set: (value = Number(), mode = "normal") => Desktop.#call("progressSet", value, mode),
|
|
59
|
-
clear: () => Desktop.#call("progressClear")
|
|
59
|
+
clear: () => Desktop.#call("progressClear"),
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
static shell = {
|
|
63
63
|
/** Открыть адрес в браузере по умолчанию; `false` — схема не разрешена */
|
|
64
|
-
openExternal: (url = String()) => Desktop.#call("shellOpenExternal", String(url))
|
|
64
|
+
openExternal: (url = String()) => Desktop.#call("shellOpenExternal", String(url)),
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
/**
|
|
@@ -78,7 +78,7 @@ class Desktop {
|
|
|
78
78
|
page: (options = {}) => Desktop.#call("printPage", options),
|
|
79
79
|
html: (html = String(), options = {}) => Desktop.#call("printHtml", String(html), options),
|
|
80
80
|
/** `{saved, path, canceled}`; с `options.path` диалог сохранения не открывается */
|
|
81
|
-
pdf: (html = String(), options = {}) => Desktop.#call("printPdf", String(html), options)
|
|
81
|
+
pdf: (html = String(), options = {}) => Desktop.#call("printPdf", String(html), options),
|
|
82
82
|
};
|
|
83
83
|
|
|
84
84
|
static shortcuts = {
|
|
@@ -86,7 +86,7 @@ class Desktop {
|
|
|
86
86
|
off: (accelerator = String(), handler = () => {}) => Desktop.#unsubscribe(accelerator, handler),
|
|
87
87
|
offAll: () => Desktop.#unsubscribeAll(),
|
|
88
88
|
/** Комбинации, на которые подписано это окно */
|
|
89
|
-
get: () => [...Desktop.#shortcut_handlers.keys()]
|
|
89
|
+
get: () => [...Desktop.#shortcut_handlers.keys()],
|
|
90
90
|
};
|
|
91
91
|
|
|
92
92
|
/**
|
|
@@ -99,14 +99,14 @@ class Desktop {
|
|
|
99
99
|
* ```
|
|
100
100
|
*/
|
|
101
101
|
static theme = {
|
|
102
|
-
SOURCE: {SYSTEM: "system", LIGHT: "light", DARK: "dark"},
|
|
102
|
+
SOURCE: { SYSTEM: "system", LIGHT: "light", DARK: "dark" },
|
|
103
103
|
/** @returns {Promise<{theme: string, source: string}>} фактическая тема и выбранный источник */
|
|
104
104
|
get: () => Desktop.#call("themeGet"),
|
|
105
105
|
set: (source = String()) => Desktop.#call("themeSet", String(source)),
|
|
106
106
|
/** Переключить светлую и тёмную */
|
|
107
107
|
toggle: () => Desktop.#call("themeToggle"),
|
|
108
108
|
/** Подписка на смену темы; возвращает функцию отписки */
|
|
109
|
-
onChange: (listener = () => {}) => Desktop.#subscribeTheme(listener)
|
|
109
|
+
onChange: (listener = () => {}) => Desktop.#subscribeTheme(listener),
|
|
110
110
|
};
|
|
111
111
|
|
|
112
112
|
/**
|
|
@@ -122,23 +122,79 @@ class Desktop {
|
|
|
122
122
|
get: () => Desktop.#call("localeGet"),
|
|
123
123
|
set: (locale = String()) => Desktop.#call("localeSet", String(locale)),
|
|
124
124
|
/** Подписка на смену языка; возвращает функцию отписки */
|
|
125
|
-
onChange: (listener = () => {}) => Desktop.#subscribeLocale(listener)
|
|
125
|
+
onChange: (listener = () => {}) => Desktop.#subscribeLocale(listener),
|
|
126
126
|
};
|
|
127
127
|
|
|
128
128
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
129
|
+
* Приложение: пути, сведения и сырые модули Electron.
|
|
130
|
+
*
|
|
131
|
+
* Странице модули main не отдают — это и есть рекомендация Electron вместо `remote`,
|
|
132
|
+
* поэтому пути и сведения идут по IPC. В main-процессе IPC нет, и те же методы отвечают
|
|
133
|
+
* значением: `path()` берёт путь у Electron, а `get()`/`getSession()`/`getWebContents()`
|
|
134
|
+
* отдают сами объекты.
|
|
131
135
|
*
|
|
132
136
|
* ```javascript
|
|
133
|
-
*
|
|
134
|
-
* await Desktop.app.info();
|
|
137
|
+
* const dir = Desktop.app.path('userData'); // путь приходит сразу, без промиса
|
|
138
|
+
* const info = await Desktop.app.info(); // {name, version, locale, isPackaged}
|
|
139
|
+
* Desktop.app.quit(); // закрыть приложение — тоже без промиса
|
|
135
140
|
* ```
|
|
136
141
|
*/
|
|
137
142
|
static app = {
|
|
138
|
-
/**
|
|
139
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Путь по имени из белого списка (`"userData"`, `"downloads"`, …).
|
|
145
|
+
*
|
|
146
|
+
* Отвечает значением, а не промисом: из этих путей страница собирает свои (файлы,
|
|
147
|
+
* логи, кеш) на старте, и `await` пришлось бы протаскивать через весь код инициализации.
|
|
148
|
+
* Идёт по синхронному каналу (`SERVICES_SYNC_CALLS`), а потому значение берётся один раз
|
|
149
|
+
* на имя и кешируется — `sendSync` останавливает страницу на время ответа, а путь за
|
|
150
|
+
* жизнь приложения не меняется (`app.setPath` в main — редкий случай, и тогда кеш
|
|
151
|
+
* страницы устареет так же, как устарел бы вызов, сделанный раньше).
|
|
152
|
+
* Прежний `await Desktop.app.path(...)` продолжает работать: `await` на строке
|
|
153
|
+
* возвращает ту же строку.
|
|
154
|
+
*
|
|
155
|
+
* Работает и в main-процессе: там канала нет, и путь берётся у Electron напрямую
|
|
156
|
+
* (с тем же белым списком), поэтому `main.js` обходится без отдельного класса путей.
|
|
157
|
+
*/
|
|
158
|
+
path: (name = String()) => Desktop.#appPath(String(name)),
|
|
140
159
|
/** @returns {Promise<{name: string, version: string, locale: string, systemLocale: string, isPackaged: boolean}>} */
|
|
141
|
-
info: () => Desktop.#call("appInfo")
|
|
160
|
+
info: () => Desktop.#call("appInfo"),
|
|
161
|
+
/**
|
|
162
|
+
* Сырой объект `app` Electron. **Только для main-процесса:** странице модули main
|
|
163
|
+
* не отдают, и здесь она получит ошибку с подсказкой — вместо `app` со страницы берут
|
|
164
|
+
* `Desktop.app.info()`, `Desktop.session.*`, `Desktop.dialog` или свой канал.
|
|
165
|
+
*
|
|
166
|
+
* ```javascript
|
|
167
|
+
* const app = Desktop.app.get(); // main.js
|
|
168
|
+
* app.on('session-created', (session) => {});
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
get: () => Desktop.#electronObject("app"),
|
|
172
|
+
/** Сырой `session` Electron (только main-процесс; странице — `Desktop.session.*`) */
|
|
173
|
+
getSession: () => Desktop.#electronObject("session"),
|
|
174
|
+
/** Сырой `webContents` Electron (только main-процесс) */
|
|
175
|
+
getWebContents: () => Desktop.#electronObject("webContents"),
|
|
176
|
+
/**
|
|
177
|
+
* Закрыть приложение: тот же выход, что у пункта меню «Выход» — `Main.stop()` снимает
|
|
178
|
+
* обработчики сервисов, глобальные клавиши и подписки, а затем приложение выходит.
|
|
179
|
+
*
|
|
180
|
+
* Отвечает значением, а не промисом (синхронный канал): ждать результат не нужно и
|
|
181
|
+
* `await` ничего не меняет — `await true` даёт то же `true`. Ответ приходит до выхода,
|
|
182
|
+
* потому что main откладывает `stop()` через `setImmediate`.
|
|
183
|
+
*
|
|
184
|
+
* ```javascript
|
|
185
|
+
* new Button({title: 'Выход', clickEvent: () => Desktop.app.quit()});
|
|
186
|
+
* ```
|
|
187
|
+
* @returns {boolean} `true` — выход начат
|
|
188
|
+
*/
|
|
189
|
+
quit: () => Desktop.#callSync("appQuitSync"),
|
|
190
|
+
/**
|
|
191
|
+
* Перезапустить приложение: процесс поднимается заново (`Main.restart()` →
|
|
192
|
+
* `app.relaunch()`), поэтому состояние из памяти не переносится — сохраните его сами.
|
|
193
|
+
*
|
|
194
|
+
* Отвечает значением, а не промисом (синхронный канал), как и `quit()`.
|
|
195
|
+
* @returns {boolean} `true` — перезапуск начат
|
|
196
|
+
*/
|
|
197
|
+
restart: () => Desktop.#callSync("appRestartSync"),
|
|
142
198
|
};
|
|
143
199
|
|
|
144
200
|
/**
|
|
@@ -152,7 +208,7 @@ class Desktop {
|
|
|
152
208
|
static dialog = {
|
|
153
209
|
open: (options = Object()) => Desktop.#call("dialogOpen", options),
|
|
154
210
|
save: (options = Object()) => Desktop.#call("dialogSave", options),
|
|
155
|
-
message: (options = Object()) => Desktop.#call("dialogMessage", options)
|
|
211
|
+
message: (options = Object()) => Desktop.#call("dialogMessage", options),
|
|
156
212
|
};
|
|
157
213
|
|
|
158
214
|
/**
|
|
@@ -167,7 +223,7 @@ class Desktop {
|
|
|
167
223
|
/** @returns {Promise<number>} размер кеша в байтах */
|
|
168
224
|
getCacheSize: () => Desktop.#call("sessionCacheSize"),
|
|
169
225
|
clearCache: () => Desktop.#call("sessionClearCache"),
|
|
170
|
-
clearStorageData: (options = Object()) => Desktop.#call("sessionClearStorage", options)
|
|
226
|
+
clearStorageData: (options = Object()) => Desktop.#call("sessionClearStorage", options),
|
|
171
227
|
};
|
|
172
228
|
|
|
173
229
|
// accelerator → набор обработчиков этого окна
|
|
@@ -183,6 +239,9 @@ class Desktop {
|
|
|
183
239
|
static #history_handlers = new Set();
|
|
184
240
|
static #history_event_bound = false;
|
|
185
241
|
static #history_bridge_unsubscribe = undefined;
|
|
242
|
+
// Пути приложения, уже полученные значением: `sendSync` останавливает страницу на время
|
|
243
|
+
// ответа, поэтому за одним и тем же именем в main ходят один раз
|
|
244
|
+
static #app_paths = new Map();
|
|
186
245
|
|
|
187
246
|
static #bridge() {
|
|
188
247
|
if (typeof window === "undefined" || window.chui === undefined) return undefined;
|
|
@@ -193,10 +252,70 @@ class Desktop {
|
|
|
193
252
|
static #call(method = String(), ...args) {
|
|
194
253
|
const services = Desktop.#bridge();
|
|
195
254
|
if (services !== undefined && typeof services[method] === "function") return services[method](...args);
|
|
196
|
-
const {ipcRenderer} = require("electron");
|
|
255
|
+
const { ipcRenderer } = require("electron");
|
|
197
256
|
return ipcRenderer.invoke(SERVICES_CALLS[method], ...args);
|
|
198
257
|
}
|
|
199
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Синхронный вызов сервиса: в безопасном режиме — метод моста, иначе — канал `sendSync`.
|
|
261
|
+
* Канал отвечает конвертом `{value}`/`{error}` (см. `SERVICES_SYNC_CALLS` в chui_channels.js),
|
|
262
|
+
* поэтому исключение из main превращается здесь в обычную ошибку с его текстом.
|
|
263
|
+
*/
|
|
264
|
+
static #callSync(method = String(), ...args) {
|
|
265
|
+
const services = Desktop.#bridge();
|
|
266
|
+
const answer =
|
|
267
|
+
services !== undefined && typeof services[method] === "function"
|
|
268
|
+
? services[method](...args)
|
|
269
|
+
: require("electron").ipcRenderer.sendSync(SERVICES_SYNC_CALLS[method], ...args);
|
|
270
|
+
if (answer === null || typeof answer !== "object") return answer;
|
|
271
|
+
if (answer.error !== undefined) throw new Error(answer.error);
|
|
272
|
+
return answer.value;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Путь приложения значением (см. `Desktop.app.path`).
|
|
277
|
+
*
|
|
278
|
+
* В main-процессе IPC недоступен (`ipcRenderer` там нет, а отправителя-страницы не
|
|
279
|
+
* существует), поэтому путь берётся у Electron напрямую — с той же проверкой белого
|
|
280
|
+
* списка, что и в канале. Это позволяет звать `Desktop.app.path()` из `main.js` и со
|
|
281
|
+
* страницы одним и тем же вызовом.
|
|
282
|
+
*/
|
|
283
|
+
static #appPath(name = String()) {
|
|
284
|
+
if (Desktop.#app_paths.has(name)) return Desktop.#app_paths.get(name);
|
|
285
|
+
const value = Desktop.#inMainProcess() ? Desktop.#appPathInMain(name) : Desktop.#callSync("appPathSync", name);
|
|
286
|
+
Desktop.#app_paths.set(name, value);
|
|
287
|
+
return value;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Различает процессы: у main нет `window`, у страницы и preload'а — есть */
|
|
291
|
+
static #inMainProcess() {
|
|
292
|
+
return typeof window === "undefined";
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** main-процесс: путь у Electron напрямую, но только из белого списка */
|
|
296
|
+
static #appPathInMain(name = String()) {
|
|
297
|
+
if (APP_PATH_NAMES.includes(name) === false) {
|
|
298
|
+
throw new Error(`Desktop.app.path: имя «${name}» вне белого списка`);
|
|
299
|
+
}
|
|
300
|
+
return require("electron").app.getPath(name);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Сырой объект Electron для main-процесса.
|
|
305
|
+
*
|
|
306
|
+
* Со страницы `app`/`session`/`webContents` недоступны: Electron не рекомендует `remote`,
|
|
307
|
+
* а мост preload'а отдаёт только узкий `window.chui`. Поэтому вместо тихого `undefined`
|
|
308
|
+
* здесь ошибка с подсказкой, что взять взамен.
|
|
309
|
+
*/
|
|
310
|
+
static #electronObject(name = String()) {
|
|
311
|
+
if (Desktop.#inMainProcess() === false) {
|
|
312
|
+
throw new Error(
|
|
313
|
+
`Desktop.app: сырой «${name}» доступен только в main-процессе — со страницы возьмите Desktop.app.info(), Desktop.session.* или свой канал из security.channels`
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
return require("electron")[name];
|
|
317
|
+
}
|
|
318
|
+
|
|
200
319
|
static #subscribe(accelerator = String(), handler = () => {}) {
|
|
201
320
|
const handlers = Desktop.#shortcut_handlers.get(accelerator) ?? new Set();
|
|
202
321
|
const first = handlers.size === 0;
|
|
@@ -243,7 +362,7 @@ class Desktop {
|
|
|
243
362
|
Desktop.#shortcut_bridge_unsubscribe = services.onShortcut(Desktop.#onShortcutEvent);
|
|
244
363
|
return;
|
|
245
364
|
}
|
|
246
|
-
const {ipcRenderer} = require("electron");
|
|
365
|
+
const { ipcRenderer } = require("electron");
|
|
247
366
|
ipcRenderer.on(SERVICES_CHANNELS.SHORTCUT_EVENT, Desktop.#ipcShortcutEvent);
|
|
248
367
|
}
|
|
249
368
|
|
|
@@ -255,7 +374,7 @@ class Desktop {
|
|
|
255
374
|
Desktop.#shortcut_bridge_unsubscribe = undefined;
|
|
256
375
|
return;
|
|
257
376
|
}
|
|
258
|
-
const {ipcRenderer} = require("electron");
|
|
377
|
+
const { ipcRenderer } = require("electron");
|
|
259
378
|
ipcRenderer.removeListener(SERVICES_CHANNELS.SHORTCUT_EVENT, Desktop.#ipcShortcutEvent);
|
|
260
379
|
}
|
|
261
380
|
|
|
@@ -288,7 +407,7 @@ class Desktop {
|
|
|
288
407
|
Desktop.#theme_bridge_unsubscribe = theme.onChange(Desktop.#onThemeEvent);
|
|
289
408
|
return;
|
|
290
409
|
}
|
|
291
|
-
const {ipcRenderer} = require("electron");
|
|
410
|
+
const { ipcRenderer } = require("electron");
|
|
292
411
|
ipcRenderer.on(SERVICES_CHANNELS.THEME_EVENT, Desktop.#ipcThemeEvent);
|
|
293
412
|
}
|
|
294
413
|
|
|
@@ -300,7 +419,7 @@ class Desktop {
|
|
|
300
419
|
Desktop.#theme_bridge_unsubscribe = undefined;
|
|
301
420
|
return;
|
|
302
421
|
}
|
|
303
|
-
const {ipcRenderer} = require("electron");
|
|
422
|
+
const { ipcRenderer } = require("electron");
|
|
304
423
|
ipcRenderer.removeListener(SERVICES_CHANNELS.THEME_EVENT, Desktop.#ipcThemeEvent);
|
|
305
424
|
}
|
|
306
425
|
|
|
@@ -333,7 +452,7 @@ class Desktop {
|
|
|
333
452
|
Desktop.#locale_bridge_unsubscribe = locale.onChange(Desktop.#onLocaleEvent);
|
|
334
453
|
return;
|
|
335
454
|
}
|
|
336
|
-
const {ipcRenderer} = require("electron");
|
|
455
|
+
const { ipcRenderer } = require("electron");
|
|
337
456
|
ipcRenderer.on(SERVICES_CHANNELS.LOCALE_EVENT, Desktop.#ipcLocaleEvent);
|
|
338
457
|
}
|
|
339
458
|
|
|
@@ -345,7 +464,7 @@ class Desktop {
|
|
|
345
464
|
Desktop.#locale_bridge_unsubscribe = undefined;
|
|
346
465
|
return;
|
|
347
466
|
}
|
|
348
|
-
const {ipcRenderer} = require("electron");
|
|
467
|
+
const { ipcRenderer } = require("electron");
|
|
349
468
|
ipcRenderer.removeListener(SERVICES_CHANNELS.LOCALE_EVENT, Desktop.#ipcLocaleEvent);
|
|
350
469
|
}
|
|
351
470
|
|
|
@@ -376,7 +495,7 @@ class Desktop {
|
|
|
376
495
|
Desktop.#history_bridge_unsubscribe = services.onClipboardHistory(Desktop.#onHistoryEvent);
|
|
377
496
|
return;
|
|
378
497
|
}
|
|
379
|
-
const {ipcRenderer} = require("electron");
|
|
498
|
+
const { ipcRenderer } = require("electron");
|
|
380
499
|
ipcRenderer.on(SERVICES_CHANNELS.CLIPBOARD_HISTORY_EVENT, Desktop.#ipcHistoryEvent);
|
|
381
500
|
}
|
|
382
501
|
|
|
@@ -388,7 +507,7 @@ class Desktop {
|
|
|
388
507
|
Desktop.#history_bridge_unsubscribe = undefined;
|
|
389
508
|
return;
|
|
390
509
|
}
|
|
391
|
-
const {ipcRenderer} = require("electron");
|
|
510
|
+
const { ipcRenderer } = require("electron");
|
|
392
511
|
ipcRenderer.removeListener(SERVICES_CHANNELS.CLIPBOARD_HISTORY_EVENT, Desktop.#ipcHistoryEvent);
|
|
393
512
|
}
|
|
394
513
|
|
|
@@ -397,4 +516,4 @@ class Desktop {
|
|
|
397
516
|
};
|
|
398
517
|
}
|
|
399
518
|
|
|
400
|
-
exports.Desktop = Desktop
|
|
519
|
+
exports.Desktop = Desktop;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const {setStyles} = require("../chui_functions");
|
|
1
|
+
const { setStyles } = require("../chui_functions");
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Перестановка элементов внутри контейнера: строки списка, карточки, пункты меню.
|
|
@@ -27,7 +27,7 @@ const {setStyles} = require("../chui_functions");
|
|
|
27
27
|
class Sortable {
|
|
28
28
|
static AXIS = {
|
|
29
29
|
Y: "y",
|
|
30
|
-
X: "x"
|
|
30
|
+
X: "x",
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
#container = undefined;
|
|
@@ -43,13 +43,16 @@ class Sortable {
|
|
|
43
43
|
#baseline = [];
|
|
44
44
|
#bound = false;
|
|
45
45
|
|
|
46
|
-
constructor(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
constructor(
|
|
47
|
+
container = new HTMLElement(),
|
|
48
|
+
options = {
|
|
49
|
+
itemSelector: String(),
|
|
50
|
+
handleSelector: String(),
|
|
51
|
+
axis: String(),
|
|
52
|
+
enabled: Boolean(),
|
|
53
|
+
onReorder: undefined,
|
|
54
|
+
}
|
|
55
|
+
) {
|
|
53
56
|
setStyles(__dirname + "/styles.css", "chUiJS_Sortable");
|
|
54
57
|
this.#container = container;
|
|
55
58
|
this.#item_selector = options.itemSelector === undefined ? "*" : options.itemSelector;
|
|
@@ -172,7 +175,7 @@ class Sortable {
|
|
|
172
175
|
this.#before = undefined;
|
|
173
176
|
if (Sortable.#sameOrder(this.#order_before, this.#items())) return;
|
|
174
177
|
this.#order_before = this.#items();
|
|
175
|
-
if (this.#on_reorder !== undefined) this.#on_reorder(this.getOrder(), {source: source, before: before});
|
|
178
|
+
if (this.#on_reorder !== undefined) this.#on_reorder(this.getOrder(), { source: source, before: before });
|
|
176
179
|
}
|
|
177
180
|
|
|
178
181
|
static #sameOrder(left = [], right = []) {
|
|
@@ -218,7 +221,9 @@ class Sortable {
|
|
|
218
221
|
const forward = this.#axis === Sortable.AXIS.X ? "ArrowRight" : "ArrowDown";
|
|
219
222
|
if (event.key !== backward && event.key !== forward) return;
|
|
220
223
|
const items = this.#items();
|
|
221
|
-
const current = items.findIndex(
|
|
224
|
+
const current = items.findIndex(
|
|
225
|
+
(item) => item === document.activeElement || item.contains(document.activeElement)
|
|
226
|
+
);
|
|
222
227
|
if (current === -1) return;
|
|
223
228
|
const next = event.key === backward ? current - 1 : current + 1;
|
|
224
229
|
if (next < 0 || next >= items.length) return;
|
|
@@ -235,4 +240,4 @@ class Sortable {
|
|
|
235
240
|
};
|
|
236
241
|
}
|
|
237
242
|
|
|
238
|
-
exports.Sortable = Sortable
|
|
243
|
+
exports.Sortable = Sortable;
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* Общие тексты берутся из словаря текущего языка (`I18n`), поэтому и они переводятся
|
|
16
16
|
* вместе с интерфейсом; свои тексты поля (`messages`) задаются строками и не переводятся.
|
|
17
17
|
*/
|
|
18
|
-
const {I18n} = require("../chui_i18n/chui_i18n");
|
|
18
|
+
const { I18n } = require("../chui_i18n/chui_i18n");
|
|
19
19
|
|
|
20
20
|
/** Ключ словаря для каждого правила: значения-параметры подставляет `I18n.t` */
|
|
21
21
|
const MESSAGE_KEYS = {
|
|
@@ -25,7 +25,7 @@ const MESSAGE_KEYS = {
|
|
|
25
25
|
minLength: "validate.minLength",
|
|
26
26
|
maxLength: "validate.maxLength",
|
|
27
27
|
pattern: "validate.pattern",
|
|
28
|
-
email: "validate.email"
|
|
28
|
+
email: "validate.email",
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
// Сборник общих сообщений остаётся доступен как прежде, но текст берётся из текущей локали:
|
|
@@ -34,7 +34,7 @@ const DEFAULT_MESSAGES = {};
|
|
|
34
34
|
for (const rule of Object.keys(MESSAGE_KEYS)) {
|
|
35
35
|
Object.defineProperty(DEFAULT_MESSAGES, rule, {
|
|
36
36
|
enumerable: true,
|
|
37
|
-
get: () => I18n.t(MESSAGE_KEYS[rule])
|
|
37
|
+
get: () => I18n.t(MESSAGE_KEYS[rule]),
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -63,7 +63,10 @@ const fail = (rule, rules) => ({ valid: false, rule: rule, message: messageFor(r
|
|
|
63
63
|
|
|
64
64
|
/** Проверка значения по правилам: {valid, rule?, message?} */
|
|
65
65
|
function check(value, rules = {}) {
|
|
66
|
-
const empty =
|
|
66
|
+
const empty =
|
|
67
|
+
typeof value === "boolean"
|
|
68
|
+
? value === false
|
|
69
|
+
: String(value === undefined || value === null ? "" : value).trim() === "";
|
|
67
70
|
if (empty) {
|
|
68
71
|
return rules.required === true ? fail("required", rules) : { valid: true };
|
|
69
72
|
}
|