sync application settings

This commit is contained in:
MaysWind
2025-06-29 20:25:21 +08:00
parent 1eb997d2c0
commit 90e862fbb1
42 changed files with 1773 additions and 81 deletions
@@ -0,0 +1,226 @@
import { ref, computed } from 'vue';
import { useSettingsStore } from '@/stores/setting.ts';
import type { ApplicationCloudSetting } from '@/core/setting.ts';
export interface CategorizedApplicationCloudSettingItems {
readonly categoryName: string;
readonly categorySubName?: string;
readonly items: ApplicationCloudSettingItem[];
}
export interface ApplicationCloudSettingItem {
readonly settingKey: string;
readonly settingName: string;
readonly mobile: boolean;
readonly desktop: boolean;
}
export const ALL_APPLICATION_CLOUD_SETTINGS: CategorizedApplicationCloudSettingItems[] = [
{
categoryName: 'Basic Settings',
items: [
{ settingKey: 'showAccountBalance', settingName: 'Show Account Balance', mobile: true, desktop: true }
]
},
{
categoryName: 'Overview Page',
items: [
{ settingKey: 'showAmountInHomePage', settingName: 'Show Amount', mobile: true, desktop: true },
{ settingKey: 'timezoneUsedForStatisticsInHomePage', settingName: 'Timezone Used for Statistics', mobile: true, desktop: true }
]
},
{
categoryName: 'Transaction List Page',
items: [
{ settingKey: 'itemsCountInTransactionListPage', settingName: 'Transactions Per Page', mobile: false, desktop: true },
{ settingKey: 'showTotalAmountInTransactionListPage', settingName: 'Show Monthly Total Amount', mobile: true, desktop: true },
{ settingKey: 'showTagInTransactionListPage', settingName: 'Show Transaction Tag', mobile: true, desktop: true }
]
},
{
categoryName: 'Transaction Edit Page',
items: [
{ settingKey: 'autoSaveTransactionDraft', settingName: 'Automatically Save Draft', mobile: true, desktop: true },
{ settingKey: 'autoGetCurrentGeoLocation', settingName: 'Automatically Add Geolocation', mobile: true, desktop: true },
{ settingKey: 'alwaysShowTransactionPicturesInMobileTransactionEditPage', settingName: 'Always Show Transaction Pictures', mobile: true, desktop: false }
]
},
{
categoryName: 'Exchange Rates Data Page',
items: [
{ settingKey: 'currencySortByInExchangeRatesPage', settingName: 'Sort by', mobile: true, desktop: true }
]
},
{
categoryName: 'Statistics Settings',
categorySubName: 'Common Settings',
items: [
{ settingKey: 'statistics.defaultChartDataType', settingName: 'Default Chart Data Type', mobile: true, desktop: true },
{ settingKey: 'statistics.defaultTimezoneType', settingName: 'Timezone Used for Date Range', mobile: true, desktop: true },
{ settingKey: 'statistics.defaultAccountFilter', settingName: 'Default Account Filter', mobile: true, desktop: true },
{ settingKey: 'statistics.defaultTransactionCategoryFilter', settingName: 'Default Transaction Category Filter', mobile: true, desktop: true },
{ settingKey: 'statistics.defaultSortingType', settingName: 'Default Sort Order', mobile: true, desktop: true }
]
},
{
categoryName: 'Statistics Settings',
categorySubName: 'Categorical Analysis Settings',
items: [
{ settingKey: 'statistics.defaultCategoricalChartType', settingName: 'Default Chart Type', mobile: true, desktop: true },
{ settingKey: 'statistics.defaultCategoricalChartDataRangeType', settingName: 'Default Date Range', mobile: true, desktop: true }
]
},
{
categoryName: 'Statistics Settings',
categorySubName: 'Trend Analysis Settings',
items: [
{ settingKey: 'statistics.defaultTrendChartType', settingName: 'Default Chart Type', mobile: false, desktop: true },
{ settingKey: 'statistics.defaultTrendChartDataRangeType', settingName: 'Default Date Range', mobile: true, desktop: true }
]
}
];
export function useAppCloudSyncBase() {
const settingsStore = useSettingsStore();
const loading = ref<boolean>(false);
const enabling = ref<boolean>(false);
const disabling = ref<boolean>(false);
const enabledApplicationCloudSettings = ref<Record<string, boolean>>(Object.assign({}, settingsStore.syncedAppSettings));
const isEnableCloudSync = computed<boolean>(() => settingsStore.enableApplicationCloudSync);
const hasEnabledApplicationCloudSettings = computed<boolean>(() => {
for (const key in enabledApplicationCloudSettings.value) {
if (!Object.prototype.hasOwnProperty.call(enabledApplicationCloudSettings.value, key)) {
continue;
}
if (enabledApplicationCloudSettings.value[key]) {
return true;
}
}
return false;
});
const enabledApplicationCloudSettingKeys = computed<string[]>(() => {
const keys: string[] = [];
for (const key in enabledApplicationCloudSettings.value) {
if (!Object.prototype.hasOwnProperty.call(enabledApplicationCloudSettings.value, key)) {
continue;
}
if (enabledApplicationCloudSettings.value[key]) {
keys.push(key);
}
}
return keys;
});
function isAllSettingsSelected(categorizedItems: CategorizedApplicationCloudSettingItems): boolean {
for (let i = 0; i < categorizedItems.items.length; i++) {
const item = categorizedItems.items[i];
if (!enabledApplicationCloudSettings.value[item.settingKey]) {
return false;
}
}
return true;
}
function hasSettingSelectedButNotAllChecked(categorizedItems: CategorizedApplicationCloudSettingItems): boolean {
let checkedCount = 0;
for (let i = 0; i < categorizedItems.items.length; i++) {
const item = categorizedItems.items[i];
if (!enabledApplicationCloudSettings.value[item.settingKey]) {
checkedCount++;
}
}
return checkedCount > 0 && checkedCount < categorizedItems.items.length;
}
function updateSettingsSelected(categorizedItems: CategorizedApplicationCloudSettingItems, value: boolean): void {
for (let i = 0; i < categorizedItems.items.length; i++) {
const item = categorizedItems.items[i];
enabledApplicationCloudSettings.value[item.settingKey] = value;
}
}
function selectAllSettings(): void {
for (let i = 0; i < ALL_APPLICATION_CLOUD_SETTINGS.length; i++) {
const categorizedItems = ALL_APPLICATION_CLOUD_SETTINGS[i];
for (let j = 0; j < categorizedItems.items.length; j++) {
const item = categorizedItems.items[j];
enabledApplicationCloudSettings.value[item.settingKey] = true;
}
}
}
function selectNoneSettings(): void {
for (let i = 0; i < ALL_APPLICATION_CLOUD_SETTINGS.length; i++) {
const categorizedItems = ALL_APPLICATION_CLOUD_SETTINGS[i];
for (let j = 0; j < categorizedItems.items.length; j++) {
const item = categorizedItems.items[j];
enabledApplicationCloudSettings.value[item.settingKey] = false;
}
}
}
function selectInvertSettings(): void {
for (let i = 0; i < ALL_APPLICATION_CLOUD_SETTINGS.length; i++) {
const categorizedItems = ALL_APPLICATION_CLOUD_SETTINGS[i];
for (let j = 0; j < categorizedItems.items.length; j++) {
const item = categorizedItems.items[j];
enabledApplicationCloudSettings.value[item.settingKey] = !enabledApplicationCloudSettings.value[item.settingKey];
}
}
}
function setUserApplicationCloudSettings(settings: ApplicationCloudSetting[] | false) {
if (settings && settings.length > 0) {
settingsStore.setApplicationSettingsFromCloudSettings(settings);
for (let i = 0; i < settings.length; i++) {
const setting = settings[i];
if (setting && setting.settingKey) {
enabledApplicationCloudSettings.value[setting.settingKey] = true;
}
}
} else {
settingsStore.setApplicationSettingsFromCloudSettings(undefined);
enabledApplicationCloudSettings.value = {};
}
}
return {
// constants
ALL_APPLICATION_CLOUD_SETTINGS,
// states
loading,
enabling,
disabling,
enabledApplicationCloudSettings,
// computed states
isEnableCloudSync,
hasEnabledApplicationCloudSettings,
enabledApplicationCloudSettingKeys,
// functions
isAllSettingsSelected,
hasSettingSelectedButNotAllChecked,
updateSettingsSelected,
selectAllSettings,
selectNoneSettings,
selectInvertSettings,
setUserApplicationCloudSettings
};
}
+13 -2
View File
@@ -13,6 +13,10 @@
<v-icon size="20" start :icon="mdiChartPieOutline"/>
{{ tt('Statistics') }}
</v-tab>
<v-tab value="cloudSyncSetting" @click="pushRouter('cloudSyncSetting')">
<v-icon size="20" start :icon="mdiCloudOutline"/>
{{ tt('Settings Sync') }}
</v-tab>
</v-tabs>
<v-window class="mt-4 disable-tab-transition" v-model="activeTab">
@@ -27,6 +31,10 @@
<v-window-item value="statisticsSetting">
<app-statistics-setting-tab/>
</v-window-item>
<v-window-item value="cloudSyncSetting">
<app-cloud-sync-setting-tab/>
</v-window-item>
</v-window>
</div>
</template>
@@ -35,6 +43,7 @@
import AppBasicSettingTab from './settings/tabs/AppBasicSettingTab.vue';
import AppLockSettingTab from './settings/tabs/AppLockSettingTab.vue';
import AppStatisticsSettingTab from './settings/tabs/AppStatisticsSettingTab.vue';
import AppCloudSyncSettingTab from './settings/tabs/AppCloudSyncSettingTab.vue';
import { ref } from 'vue';
import { useRouter, onBeforeRouteUpdate } from 'vue-router';
@@ -44,7 +53,8 @@ import { useI18n } from '@/locales/helpers.ts';
import {
mdiCogOutline,
mdiLockOpenOutline,
mdiChartPieOutline
mdiChartPieOutline,
mdiCloudOutline
} from '@mdi/js';
const props = defineProps<{
@@ -58,7 +68,8 @@ const { tt } = useI18n();
const ALL_TABS: string[] = [
'basicSetting',
'applicationLockSetting',
'statisticsSetting'
'statisticsSetting',
'cloudSyncSetting'
];
const activeTab = ref<string>((() => {
@@ -0,0 +1,236 @@
<template>
<v-row>
<v-col cols="12">
<v-card>
<template #title>
<span>{{ tt('Settings Sync') }}</span>
<v-progress-circular indeterminate size="20" class="ml-3" v-if="loading"></v-progress-circular>
</template>
<v-card-text class="pb-0">
<v-skeleton-loader class="skeleton-no-margin pt-2 pb-5" type="text" style="width: 150px" :loading="true" v-if="loading"></v-skeleton-loader>
<p class="text-body-1" v-if="!loading && !isEnableCloudSync">
{{ tt('Settings sync is not enabled') }}
</p>
<p class="text-body-1" v-if="!loading && isEnableCloudSync">
{{ tt('Settings sync has been enabled') }}
</p>
</v-card-text>
<v-card-text>
<v-expansion-panels class="synchronized-settings" multiple
:readonly="true" :hide-actions="true"
:disabled="loading || enabling || disabling"
v-model="openedPanel">
<v-expansion-panel class="border" value="synchronizedSettings">
<v-expansion-panel-title class="expand-panel-title-with-bg py-0">
<div class="d-flex align-center justify-center w-100">
<div class="w-100">
<span>{{ tt('Synchronized Settings') }}</span>
</div>
<v-btn density="comfortable" color="default" variant="text" class="ml-2"
:disabled="loading || enabling || disabling" :icon="true">
<v-icon :icon="mdiDotsVertical" />
<v-menu activator="parent">
<v-list>
<v-list-item :disable="loading || enabling || disabling"
:prepend-icon="mdiSelectAll"
:title="tt('Select All')"
@click="selectAllSettings"></v-list-item>
<v-list-item :disable="loading || enabling || disabling"
:prepend-icon="mdiSelect"
:title="tt('Select None')"
@click="selectNoneSettings"></v-list-item>
<v-list-item :disable="loading || enabling || disabling"
:prepend-icon="mdiSelectInverse"
:title="tt('Invert Selection')"
@click="selectInvertSettings"></v-list-item>
</v-list>
</v-menu>
</v-btn>
</div>
</v-expansion-panel-title>
<v-expansion-panel-text>
<v-list rounded density="comfortable" class="pa-0">
<template :key="categorizedItems.categoryName"
v-for="(categorizedItems, categoryIdx) in ALL_APPLICATION_CLOUD_SETTINGS">
<v-divider v-if="categoryIdx > 0"/>
<v-list-item>
<template #prepend>
<v-checkbox :disabled="loading || enabling || disabling"
:model-value="isAllSettingsSelected(categorizedItems)"
:indeterminate="hasSettingSelectedButNotAllChecked(categorizedItems)"
@update:model-value="updateSettingsSelected(categorizedItems, !!$event)">
<template #label>
<span>{{ tt(categorizedItems.categoryName) }}</span>
<span class="mx-2" v-if="categorizedItems.categorySubName">/</span>
<span v-if="categorizedItems.categorySubName">{{ tt(categorizedItems.categorySubName) }}</span>
</template>
</v-checkbox>
</template>
</v-list-item>
<v-divider/>
<v-list rounded density="comfortable" class="pa-0 ml-4">
<template :key="settingItem.settingKey"
v-for="(settingItem, itemIdx) in categorizedItems.items">
<v-divider v-if="itemIdx > 0"/>
<v-list-item>
<template #prepend>
<v-checkbox :disabled="loading || enabling || disabling"
:model-value="enabledApplicationCloudSettings[settingItem.settingKey]"
@update:model-value="enabledApplicationCloudSettings[settingItem.settingKey] = !!$event">
<template #label>
<span>{{ tt(settingItem.settingName) }}</span>
<v-icon class="ml-2 mr-0" start size="16" :icon="mdiCellphone" v-if="settingItem.mobile"/>
<v-icon class="ml-2 mr-0" start size="16" :icon="mdiMonitor" v-if="settingItem.desktop"/>
</template>
</v-checkbox>
</template>
</v-list-item>
</template>
</v-list>
</template>
</v-list>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</v-card-text>
<v-card-text>
<v-row>
<v-col cols="12" class="d-flex flex-wrap gap-4">
<v-btn :disabled="loading || enabling || disabling || !hasEnabledApplicationCloudSettings" v-if="!isEnableCloudSync" @click="enable(false)">
{{ tt('Enable Settings Sync') }}
<v-progress-circular indeterminate size="22" class="ml-2" v-if="enabling"></v-progress-circular>
</v-btn>
<v-btn :disabled="loading || enabling || disabling || !hasEnabledApplicationCloudSettings" v-if="isEnableCloudSync" @click="enable(true)">
{{ tt('Update Synchronized Settings') }}
<v-progress-circular indeterminate size="22" class="ml-2" v-if="enabling"></v-progress-circular>
</v-btn>
<v-btn :disabled="loading || enabling || disabling" v-if="isEnableCloudSync" @click="disable">
{{ tt('Disable Settings Sync') }}
<v-progress-circular indeterminate size="22" class="ml-2" v-if="disabling"></v-progress-circular>
</v-btn>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
</v-row>
<snack-bar ref="snackbar" />
</template>
<script setup lang="ts">
import SnackBar from '@/components/desktop/SnackBar.vue';
import { ref, useTemplateRef } from 'vue';
import { useI18n } from '@/locales/helpers.ts';
import { useAppCloudSyncBase } from '@/views/base/settings/AppCloudSyncPageBase.ts';
import { useUserStore } from '@/stores/user.ts';
import {
mdiDotsVertical,
mdiSelectAll,
mdiSelect,
mdiSelectInverse,
mdiCellphone,
mdiMonitor,
} from '@mdi/js';
type SnackBarType = InstanceType<typeof SnackBar>;
const { tt } = useI18n();
const {
ALL_APPLICATION_CLOUD_SETTINGS,
loading,
enabling,
disabling,
enabledApplicationCloudSettings,
isEnableCloudSync,
hasEnabledApplicationCloudSettings,
enabledApplicationCloudSettingKeys,
isAllSettingsSelected,
hasSettingSelectedButNotAllChecked,
updateSettingsSelected,
selectAllSettings,
selectNoneSettings,
selectInvertSettings,
setUserApplicationCloudSettings
} = useAppCloudSyncBase();
const userStore = useUserStore();
const snackbar = useTemplateRef<SnackBarType>('snackbar');
const openedPanel = ref<string[]>(['synchronizedSettings']);
function init(): void {
loading.value = true;
userStore.getUserApplicationCloudSettings().then(response => {
setUserApplicationCloudSettings(response);
loading.value = false;
}).catch(error => {
loading.value = false;
if (!error.processed) {
snackbar.value?.showError(error);
}
});
}
function enable(update: boolean): void {
enabling.value = true;
userStore.fullUpdateUserApplicationCloudSettings(enabledApplicationCloudSettingKeys.value).then(() => {
enabling.value = false;
if (!update) {
snackbar.value?.showMessage('Settings sync has been enabled');
} else {
snackbar.value?.showMessage('Synchronized settings have been updated');
}
}).catch(error => {
enabling.value = false;
if (!error.processed) {
snackbar.value?.showError(error);
}
});
}
function disable(): void {
disabling.value = true;
userStore.disableUserApplicationCloudSettings().then(() => {
enabledApplicationCloudSettings.value = {};
disabling.value = false;
snackbar.value?.showMessage('Settings sync has been disabled');
}).catch(error => {
disabling.value = false;
if (!error.processed) {
snackbar.value?.showError(error);
}
});
}
init();
</script>
<style>
.synchronized-settings .v-expansion-panel-title {
cursor: inherit;
}
.synchronized-settings .v-expansion-panel-text__wrapper {
padding: 0 0 0 0;
}
</style>
+1 -1
View File
@@ -72,8 +72,8 @@
</f7-list-item>
<f7-list-item :title="tt('Page Settings')" link="/settings/page"></f7-list-item>
<f7-list-item :title="tt('Statistics Settings')" link="/statistic/settings"></f7-list-item>
<f7-list-item :title="tt('Settings Sync')" link="/settings/sync"></f7-list-item>
<f7-list-item>
<span>{{ tt('Enable Animation') }}</span>
@@ -0,0 +1,197 @@
<template>
<f7-page @page:afterin="onPageAfterIn">
<f7-navbar>
<f7-nav-left :back-link="tt('Back')"></f7-nav-left>
<f7-nav-title :title="tt('Settings Sync')"></f7-nav-title>
<f7-nav-right>
<f7-link icon-f7="ellipsis" :class="{ 'disabled': loading || enabling || disabling }" @click="showMoreActionSheet = true"></f7-link>
</f7-nav-right>
</f7-navbar>
<f7-list strong inset dividers class="margin-vertical skeleton-text" v-if="loading">
<f7-list-item title="Status" after="Unknown"></f7-list-item>
</f7-list>
<f7-list strong inset dividers class="margin-vertical" v-else-if="!loading">
<f7-list-item :title="tt('Status')" :after="tt(isEnableCloudSync ? 'Enabled' : 'Disabled')"></f7-list-item>
</f7-list>
<f7-list strong inset dividers class="margin-vertical synchronized-settings-list"
:class="{ 'disabled': loading || enabling || disabling }">
<f7-list-item group-title :sortable="false">
<small>{{ tt('Synchronized Settings') }}</small>
</f7-list-item>
<f7-list-item class="has-child-list-item" checkbox
:disabled="loading || enabling || disabling"
:title="tt(categorizedItems.categoryName)"
:value="categorizedItems.categoryName"
:checked="isAllSettingsSelected(categorizedItems)"
:indeterminate="hasSettingSelectedButNotAllChecked(categorizedItems)"
:key="categorizedItems.categoryName"
v-for="categorizedItems in ALL_APPLICATION_CLOUD_SETTINGS"
@change="updateSettingsSelected(categorizedItems, $event.target.checked)">
<template #root>
<ul class="padding-left">
<f7-list-item checkbox
:disabled="loading || enabling || disabling"
:title="tt(settingItem.settingName)"
:value="settingItem.settingKey"
:checked="enabledApplicationCloudSettings[settingItem.settingKey]"
:key="settingItem.settingKey"
v-for="settingItem in categorizedItems.items"
@change="enabledApplicationCloudSettings[settingItem.settingKey] = $event.target.checked">
<template #after>
<f7-icon class="synchronized-settings-device-icon" f7="device_phone_portrait" v-if="settingItem.mobile"></f7-icon>
<f7-icon class="synchronized-settings-device-icon" f7="device_desktop" v-if="settingItem.desktop"></f7-icon>
</template>
</f7-list-item>
</ul>
</template>
</f7-list-item>
</f7-list>
<f7-list strong inset dividers class="margin-vertical skeleton-text" v-if="loading">
<f7-list-button class="disabled">Operate</f7-list-button>
</f7-list>
<f7-list strong inset dividers class="margin-vertical" v-else-if="!loading">
<f7-list-button :class="{ 'disabled': loading || enabling || disabling || !hasEnabledApplicationCloudSettings }"
v-if="!isEnableCloudSync"
@click="enable(false)">{{ tt('Enable Settings Sync') }}</f7-list-button>
<f7-list-button :class="{ 'disabled': loading || enabling || disabling || !hasEnabledApplicationCloudSettings }"
v-if="isEnableCloudSync"
@click="enable(true)">{{ tt('Update Synchronized Settings') }}</f7-list-button>
<f7-list-button :class="{ 'disabled': loading || enabling || disabling }"
v-if="isEnableCloudSync"
@click="disable">{{ tt('Disable') }}</f7-list-button>
</f7-list>
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
<f7-actions-group>
<f7-actions-button :class="{ 'disabled': loading || enabling || disabling }"
@click="selectAllSettings">{{ tt('Select All') }}</f7-actions-button>
<f7-actions-button :class="{ 'disabled': loading || enabling || disabling }"
@click="selectNoneSettings">{{ tt('Select None') }}</f7-actions-button>
<f7-actions-button :class="{ 'disabled': loading || enabling || disabling }"
@click="selectInvertSettings">{{ tt('Invert Selection') }}</f7-actions-button>
</f7-actions-group>
<f7-actions-group>
<f7-actions-button bold close>{{ tt('Cancel') }}</f7-actions-button>
</f7-actions-group>
</f7-actions>
</f7-page>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import type { Router } from 'framework7/types';
import { useI18n } from '@/locales/helpers.ts';
import { useI18nUIComponents, showLoading, hideLoading } from '@/lib/ui/mobile.ts';
import { useAppCloudSyncBase } from '@/views/base/settings/AppCloudSyncPageBase.ts';
import { useUserStore } from '@/stores/user.ts';
const props = defineProps<{
f7router: Router.Router;
}>();
const { tt } = useI18n();
const { showToast, routeBackOnError } = useI18nUIComponents();
const {
ALL_APPLICATION_CLOUD_SETTINGS,
loading,
enabling,
disabling,
enabledApplicationCloudSettings,
isEnableCloudSync,
hasEnabledApplicationCloudSettings,
enabledApplicationCloudSettingKeys,
isAllSettingsSelected,
hasSettingSelectedButNotAllChecked,
updateSettingsSelected,
selectAllSettings,
selectNoneSettings,
selectInvertSettings,
setUserApplicationCloudSettings
} = useAppCloudSyncBase();
const userStore = useUserStore();
const loadingError = ref<unknown | null>(null);
const showMoreActionSheet = ref<boolean>(false);
function init(): void {
loading.value = true;
userStore.getUserApplicationCloudSettings().then(response => {
setUserApplicationCloudSettings(response);
loading.value = false;
}).catch(error => {
if (error.processed) {
loading.value = false;
} else {
loadingError.value = error;
showToast(error.message || error);
}
});
}
function enable(update: boolean): void {
enabling.value = true;
showLoading(() => enabling.value);
userStore.fullUpdateUserApplicationCloudSettings(enabledApplicationCloudSettingKeys.value).then(() => {
enabling.value = false;
hideLoading();
if (!update) {
showToast('Settings sync has been enabled');
} else {
showToast('Synchronized settings have been updated');
}
}).catch(error => {
enabling.value = false;
hideLoading();
if (!error.processed) {
showToast(error.message || error);
}
});
}
function disable(): void {
disabling.value = true;
showLoading(() => disabling.value);
userStore.disableUserApplicationCloudSettings().then(() => {
enabledApplicationCloudSettings.value = {};
disabling.value = false;
hideLoading();
showToast('Settings sync has been disabled');
}).catch(error => {
disabling.value = false;
hideLoading();
if (!error.processed) {
showToast(error.message || error);
}
});
}
function onPageAfterIn(): void {
routeBackOnError(props.f7router, loadingError);
}
init();
</script>
<style>
.synchronized-settings-list {
--f7-list-group-title-height: var(--ebk-synchronized-settings-list-group-title-height);
}
.synchronized-settings-device-icon {
font-size: var(--ebk-synchronized-settings-list-device-icon-font-size);
}
</style>