MOBILE-3565 settings: Add general settings page
parent
3be2f204f4
commit
a71aa53dae
|
@ -84,9 +84,9 @@
|
|||
}
|
||||
},
|
||||
"font_sizes": [
|
||||
62.5,
|
||||
75.89,
|
||||
93.75
|
||||
100,
|
||||
110,
|
||||
120
|
||||
],
|
||||
"customurlscheme": "moodlemobile",
|
||||
"siteurl": "",
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
|
||||
<ion-title>{{ 'core.settings.general' | translate }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.settings.language' | translate }}</h2>
|
||||
</ion-label>
|
||||
<ion-select [(ngModel)]="selectedLanguage" (ionChange)="languageChanged()" interface="action-sheet"
|
||||
[interfaceOptions]="{header: 'core.settings.language' | translate}">
|
||||
<ion-select-option *ngFor="let entry of languages" [value]="entry.code">{{ entry.name }}</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap core-settings-general-font-size item-interactive">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.settings.fontsize' | translate }}</h2>
|
||||
</ion-label>
|
||||
<ion-segment [(ngModel)]="selectedFontSize" (ionChange)="fontSizeChanged()" color="primary" item-content>
|
||||
<ion-segment-button *ngFor="let fontSize of fontSizes" [value]="fontSize.size"
|
||||
[ngStyle]="{'font-size.px': fontSize.style}">
|
||||
{{ 'core.settings.fontsizecharacter' | translate }}
|
||||
<!-- Empty element styled with the largest font size, so all buttons share a common baseline. -->
|
||||
<span [ngStyle]="{'font-size.px': fontSizes[fontSizes.length - 1].style}"></span>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap core-settings-general-color-scheme" *ngIf="colorSchemes.length > 0">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.settings.colorscheme' | translate }}</h2>
|
||||
<p *ngIf="colorSchemeDisabled" class="text-danger">{{ 'core.settings.forcedsetting' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-select [(ngModel)]="selectedScheme" (ionChange)="colorSchemeChanged()" interface="action-sheet"
|
||||
[disabled]="colorSchemeDisabled" [interfaceOptions]="{header: 'core.settings.colorscheme' | translate}">
|
||||
<ion-select-option *ngFor="let scheme of colorSchemes" [value]="scheme">
|
||||
{{ 'core.settings.colorscheme-' + scheme | translate }}</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.settings.enablerichtexteditor' | translate }}</h2>
|
||||
<p>{{ 'core.settings.enablerichtexteditordescription' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-toggle [(ngModel)]="richTextEditor" (ionChange)="richTextEditorChanged()"></ion-toggle>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.settings.debugdisplay' | translate }}</h2>
|
||||
<p>{{ 'core.settings.debugdisplaydescription' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-toggle [(ngModel)]="debugDisplay" (ionChange)="debugDisplayChanged()"></ion-toggle>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="analyticsSupported">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.settings.enablefirebaseanalytics' | translate }}</h2>
|
||||
<p>{{ 'core.settings.enablefirebaseanalyticsdescription' | translate }}</p>
|
||||
</ion-label>
|
||||
<ion-toggle [(ngModel)]="analyticsEnabled" (ionChange)="analyticsEnabledChanged()"></ion-toggle>
|
||||
</ion-item>
|
||||
</ion-content>
|
|
@ -0,0 +1,48 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { IonicModule } from '@ionic/angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
|
||||
import { CoreSettingsGeneralPage } from './general.page';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: CoreSettingsGeneralPage,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
CoreSettingsGeneralPage,
|
||||
],
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
FormsModule,
|
||||
],
|
||||
})
|
||||
export class CoreSettingsGeneralPageModule {}
|
|
@ -0,0 +1,168 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
import { CoreConfig } from '@services/config';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreLang } from '@services/lang';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
// import { CorePushNotifications } from '@core/pushnotifications/services/pushnotifications';
|
||||
import { CoreSettingsHelper, CoreColorScheme } from '../../services/settings.helper';
|
||||
|
||||
/**
|
||||
* Page that displays the general settings.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-core-settings-general',
|
||||
templateUrl: 'general.html',
|
||||
styleUrls: ['general.scss'],
|
||||
})
|
||||
export class CoreSettingsGeneralPage {
|
||||
|
||||
languages: { code: string; name: string }[] = [];
|
||||
selectedLanguage = '';
|
||||
fontSizes: { size: number; style: number; selected: boolean }[] = [];
|
||||
selectedFontSize = 0;
|
||||
richTextEditor = true;
|
||||
debugDisplay = false;
|
||||
analyticsSupported = false;
|
||||
analyticsEnabled = false;
|
||||
colorSchemes: CoreColorScheme[] = [];
|
||||
selectedScheme: CoreColorScheme = CoreColorScheme.LIGHT;
|
||||
colorSchemeDisabled = false;
|
||||
|
||||
constructor() {
|
||||
this.asyncInit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Async part of the constructor.
|
||||
*/
|
||||
protected async asyncInit(): Promise<void> {
|
||||
|
||||
// Get the supported languages.
|
||||
const languages = CoreConstants.CONFIG.languages;
|
||||
for (const code in languages) {
|
||||
this.languages.push({
|
||||
code: code,
|
||||
name: languages[code],
|
||||
});
|
||||
}
|
||||
// Sort them by name.
|
||||
this.languages.sort((a, b) => a.name.localeCompare(b.name));
|
||||
this.selectedLanguage = await CoreLang.instance.getCurrentLanguage();
|
||||
|
||||
// Configure color schemes.
|
||||
if (!CoreConstants.CONFIG.forceColorScheme) {
|
||||
this.colorSchemeDisabled = CoreSettingsHelper.instance.isColorSchemeDisabledInSite();
|
||||
|
||||
if (this.colorSchemeDisabled) {
|
||||
this.colorSchemes.push(CoreColorScheme.LIGHT);
|
||||
this.selectedScheme = this.colorSchemes[0];
|
||||
} else {
|
||||
this.colorSchemes.push(CoreColorScheme.LIGHT);
|
||||
this.colorSchemes.push(CoreColorScheme.DARK);
|
||||
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches ||
|
||||
window.matchMedia('(prefers-color-scheme: light)').matches) {
|
||||
this.colorSchemes.push(CoreColorScheme.AUTO);
|
||||
}
|
||||
|
||||
this.selectedScheme = await CoreConfig.instance.get(CoreConstants.SETTINGS_COLOR_SCHEME, CoreColorScheme.LIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
this.selectedFontSize = await CoreConfig.instance.get(
|
||||
CoreConstants.SETTINGS_FONT_SIZE,
|
||||
CoreConstants.CONFIG.font_sizes[0],
|
||||
);
|
||||
|
||||
this.fontSizes = CoreConstants.CONFIG.font_sizes.map((size) =>
|
||||
({
|
||||
size,
|
||||
// Absolute pixel size based on 1.4rem body text when this size is selected.
|
||||
style: Math.round(size * 16 / 100),
|
||||
selected: size === this.selectedFontSize,
|
||||
}));
|
||||
|
||||
|
||||
this.richTextEditor = await CoreConfig.instance.get(CoreConstants.SETTINGS_RICH_TEXT_EDITOR, true);
|
||||
|
||||
this.debugDisplay = await CoreConfig.instance.get(CoreConstants.SETTINGS_DEBUG_DISPLAY, false);
|
||||
|
||||
this.analyticsSupported = CoreConstants.CONFIG.enableanalytics;
|
||||
if (this.analyticsSupported) {
|
||||
this.analyticsEnabled = await CoreConfig.instance.get(CoreConstants.SETTINGS_ANALYTICS_ENABLED, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a new language is selected.
|
||||
*/
|
||||
languageChanged(): void {
|
||||
CoreLang.instance.changeCurrentLanguage(this.selectedLanguage).finally(() => {
|
||||
CoreEvents.trigger(CoreEvents.LANGUAGE_CHANGED, this.selectedLanguage);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a new font size is selected.
|
||||
*/
|
||||
fontSizeChanged(): void {
|
||||
this.fontSizes = this.fontSizes.map((fontSize) => {
|
||||
fontSize.selected = fontSize.size === this.selectedFontSize;
|
||||
|
||||
return fontSize;
|
||||
});
|
||||
|
||||
CoreSettingsHelper.instance.setFontSize(this.selectedFontSize);
|
||||
CoreConfig.instance.set(CoreConstants.SETTINGS_FONT_SIZE, this.selectedFontSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a new color scheme is selected.
|
||||
*/
|
||||
colorSchemeChanged(): void {
|
||||
CoreSettingsHelper.instance.setColorScheme(this.selectedScheme);
|
||||
CoreConfig.instance.set(CoreConstants.SETTINGS_COLOR_SCHEME, this.selectedScheme);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the rich text editor is enabled or disabled.
|
||||
*/
|
||||
richTextEditorChanged(): void {
|
||||
CoreConfig.instance.set(CoreConstants.SETTINGS_RICH_TEXT_EDITOR, this.richTextEditor ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the debug display setting is enabled or disabled.
|
||||
*/
|
||||
debugDisplayChanged(): void {
|
||||
CoreConfig.instance.set(CoreConstants.SETTINGS_DEBUG_DISPLAY, this.debugDisplay ? 1 : 0);
|
||||
CoreDomUtils.instance.setDebugDisplay(this.debugDisplay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the analytics setting is enabled or disabled.
|
||||
*
|
||||
* @todo
|
||||
*/
|
||||
async analyticsEnabledChanged(): Promise<void> {
|
||||
// await this.pushNotificationsProvider.enableAnalytics(this.analyticsEnabled);
|
||||
|
||||
CoreConfig.instance.set(CoreConstants.SETTINGS_ANALYTICS_ENABLED, this.analyticsEnabled ? 1 : 0);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
:host {
|
||||
.core-settings-general-font-size ion-segment {
|
||||
max-width: 250px;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,431 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreCron } from '@services/cron';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreFilepool } from '@services/filepool';
|
||||
import { CoreSite } from '@classes/site';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
import { CoreConfig } from '@services/config';
|
||||
// import { CoreFilterProvider } from '@core/filter/providers/filter';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
// import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
import { makeSingleton, Translate } from '@singletons/core.singletons';
|
||||
|
||||
/**
|
||||
* Object with space usage and cache entries that can be erased.
|
||||
*/
|
||||
export interface CoreSiteSpaceUsage {
|
||||
cacheEntries?: number; // Number of cached entries that can be cleared.
|
||||
spaceUsage?: number; // Space used in this site (total files + estimate of cache).
|
||||
}
|
||||
|
||||
/**
|
||||
* Constants to define color schemes.
|
||||
*/
|
||||
export const enum CoreColorScheme {
|
||||
AUTO = 'auto',
|
||||
LIGHT = 'light',
|
||||
DARK = 'dark',
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings helper service.
|
||||
*/
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class CoreSettingsHelperProvider {
|
||||
|
||||
protected syncPromises: { [s: string]: Promise<void> } = {};
|
||||
protected prefersDark?: MediaQueryList;
|
||||
|
||||
constructor() {
|
||||
// protected filterProvider: CoreFilterProvider,
|
||||
// protected courseProvider: CoreCourseProvider,
|
||||
|
||||
if (!CoreConstants.CONFIG.forceColorScheme) {
|
||||
// Update color scheme when a user enters or leaves a site, or when the site info is updated.
|
||||
const applySiteScheme = (): void => {
|
||||
if (this.isColorSchemeDisabledInSite()) {
|
||||
// Dark mode is disabled, force light mode.
|
||||
this.setColorScheme(CoreColorScheme.LIGHT);
|
||||
} else {
|
||||
this.prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
// Reset color scheme settings.
|
||||
this.initColorScheme();
|
||||
}
|
||||
};
|
||||
|
||||
CoreEvents.on(CoreEvents.LOGIN, applySiteScheme.bind(this));
|
||||
|
||||
CoreEvents.on(CoreEvents.SITE_UPDATED, applySiteScheme.bind(this));
|
||||
|
||||
CoreEvents.on(CoreEvents.LOGOUT, () => {
|
||||
// Reset color scheme settings.
|
||||
this.initColorScheme();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes files of a site and the tables that can be cleared.
|
||||
*
|
||||
* @param siteName Site Name.
|
||||
* @param siteId: Site ID.
|
||||
* @return Resolved with detailed new info when done.
|
||||
* @todo filterProvider and courseProviderpart.
|
||||
*/
|
||||
async deleteSiteStorage(siteName: string, siteId: string): Promise<CoreSiteSpaceUsage> {
|
||||
const siteInfo: CoreSiteSpaceUsage = {
|
||||
cacheEntries: 0,
|
||||
spaceUsage: 0,
|
||||
};
|
||||
|
||||
// siteName = await this.filterProvider.formatText(siteName, { clean: true, singleLine: true, filter: false }, [], siteId);
|
||||
|
||||
const title = Translate.instance.instant('core.settings.deletesitefilestitle');
|
||||
const message = Translate.instance.instant('core.settings.deletesitefiles', { sitename: siteName });
|
||||
|
||||
await CoreDomUtils.instance.showConfirm(message, title);
|
||||
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
// Clear cache tables.
|
||||
const cleanSchemas = CoreSites.instance.getSiteTableSchemasToClear(site);
|
||||
const promises: Promise<number | void>[] = cleanSchemas.map((name) => site.getDb().deleteRecords(name));
|
||||
const filepoolService = CoreFilepool.instance;
|
||||
|
||||
|
||||
promises.push(site.deleteFolder().then(() => {
|
||||
filepoolService.clearAllPackagesStatus(siteId);
|
||||
filepoolService.clearFilepool(siteId);
|
||||
// this.courseProvider.clearAllCoursesStatus(siteId);
|
||||
|
||||
siteInfo.spaceUsage = 0;
|
||||
|
||||
return;
|
||||
}).catch(async (error) => {
|
||||
if (error && error.code === FileError.NOT_FOUND_ERR) {
|
||||
// Not found, set size 0.
|
||||
filepoolService.clearAllPackagesStatus(siteId);
|
||||
siteInfo.spaceUsage = 0;
|
||||
} else {
|
||||
// Error, recalculate the site usage.
|
||||
CoreDomUtils.instance.showErrorModal('core.settings.errordeletesitefiles', true);
|
||||
|
||||
siteInfo.spaceUsage = await site.getSpaceUsage();
|
||||
}
|
||||
}).then(async () => {
|
||||
CoreEvents.trigger(CoreEvents.SITE_STORAGE_DELETED, {}, siteId);
|
||||
|
||||
siteInfo.cacheEntries = await this.calcSiteClearRows(site);
|
||||
|
||||
return;
|
||||
}));
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
return siteInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates each site's usage, and the total usage.
|
||||
*
|
||||
* @param siteId ID of the site. Current site if undefined.
|
||||
* @return Resolved with detailed info when done.
|
||||
*/
|
||||
async getSiteSpaceUsage(siteId?: string): Promise<CoreSiteSpaceUsage> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
// Get space usage.
|
||||
const siteInfo: CoreSiteSpaceUsage = {
|
||||
cacheEntries: 0,
|
||||
spaceUsage: 0,
|
||||
};
|
||||
|
||||
siteInfo.cacheEntries = await this.calcSiteClearRows(site);
|
||||
siteInfo.spaceUsage = await site.getTotalUsage();
|
||||
|
||||
return siteInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of rows to be deleted on a site.
|
||||
*
|
||||
* @param site Site object.
|
||||
* @return If there are rows to delete or not.
|
||||
*/
|
||||
protected async calcSiteClearRows(site: CoreSite): Promise<number> {
|
||||
const clearTables = CoreSites.instance.getSiteTableSchemasToClear(site);
|
||||
|
||||
let totalEntries = 0;
|
||||
|
||||
await Promise.all(clearTables.map(async (name) =>
|
||||
totalEntries = await site.getDb().countRecords(name) + totalEntries));
|
||||
|
||||
return totalEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a certain processor from a list of processors.
|
||||
*
|
||||
* @param processors List of processors.
|
||||
* @param name Name of the processor to get.
|
||||
* @param fallback True to return first processor if not found, false to not return any. Defaults to true.
|
||||
* @return Processor.
|
||||
* @todo
|
||||
*/
|
||||
getProcessor(processors: any[], name: string, fallback: boolean = true): any {
|
||||
if (!processors || !processors.length) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < processors.length; i++) {
|
||||
if (processors[i].name == name) {
|
||||
return processors[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Processor not found, return first if requested.
|
||||
if (fallback) {
|
||||
return processors[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the components and notifications that have a certain processor.
|
||||
*
|
||||
* @param processor Name of the processor to filter.
|
||||
* @param components Array of components.
|
||||
* @return Filtered components.
|
||||
* @todo
|
||||
*/
|
||||
getProcessorComponents(processor: string, components: any[]): any[] {
|
||||
return processor? components : [];
|
||||
/*
|
||||
const result = [];
|
||||
|
||||
components.forEach((component) => {
|
||||
// Create a copy of the component with an empty list of notifications.
|
||||
const componentCopy = CoreUtils.instance.clone(component);
|
||||
componentCopy.notifications = [];
|
||||
|
||||
component.notifications.forEach((notification) => {
|
||||
let hasProcessor = false;
|
||||
for (let i = 0; i < notification.processors.length; i++) {
|
||||
const proc = notification.processors[i];
|
||||
if (proc.name == processor) {
|
||||
hasProcessor = true;
|
||||
notification.currentProcessor = proc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasProcessor) {
|
||||
// Add the notification.
|
||||
componentCopy.notifications.push(notification);
|
||||
}
|
||||
});
|
||||
|
||||
if (componentCopy.notifications.length) {
|
||||
// At least 1 notification added, add the component to the result.
|
||||
result.push(componentCopy);
|
||||
}
|
||||
});
|
||||
|
||||
return result;*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the synchronization promise of a site.
|
||||
*
|
||||
* @param siteId ID of the site.
|
||||
* @return Sync promise or null if site is not being syncrhonized.
|
||||
*/
|
||||
async getSiteSyncPromise(siteId: string): Promise<void> {
|
||||
if (this.syncPromises[siteId]) {
|
||||
return this.syncPromises[siteId];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize a site.
|
||||
*
|
||||
* @param syncOnlyOnWifi True to sync only on wifi, false otherwise.
|
||||
* @param siteId ID of the site to synchronize.
|
||||
* @return Promise resolved when synchronized, rejected if failure.
|
||||
*/
|
||||
async synchronizeSite(syncOnlyOnWifi: boolean, siteId: string): Promise<void> {
|
||||
if (this.syncPromises[siteId]) {
|
||||
// There's already a sync ongoing for this site, return the promise.
|
||||
return this.syncPromises[siteId];
|
||||
}
|
||||
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
const hasSyncHandlers = CoreCron.instance.hasManualSyncHandlers();
|
||||
|
||||
if (site.isLoggedOut()) {
|
||||
// Cannot sync logged out sites.
|
||||
throw Translate.instance.instant('core.settings.cannotsyncloggedout');
|
||||
} else if (hasSyncHandlers && !CoreApp.instance.isOnline()) {
|
||||
// We need connection to execute sync.
|
||||
throw Translate.instance.instant('core.settings.cannotsyncoffline');
|
||||
} else if (hasSyncHandlers && syncOnlyOnWifi && CoreApp.instance.isNetworkAccessLimited()) {
|
||||
throw Translate.instance.instant('core.settings.cannotsyncwithoutwifi');
|
||||
}
|
||||
|
||||
const syncPromise = Promise.all([
|
||||
// Invalidate all the site files so they are re-downloaded.
|
||||
CoreUtils.instance.ignoreErrors(CoreFilepool.instance.invalidateAllFiles(siteId)),
|
||||
// Invalidate and synchronize site data.
|
||||
site.invalidateWsCache(),
|
||||
this.checkSiteLocalMobile(site),
|
||||
CoreSites.instance.updateSiteInfo(site.getId()),
|
||||
CoreCron.instance.forceSyncExecution(site.getId()),
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
]).then(() => {
|
||||
return;
|
||||
});
|
||||
|
||||
this.syncPromises[siteId] = syncPromise;
|
||||
|
||||
try {
|
||||
await syncPromise;
|
||||
} finally {
|
||||
delete this.syncPromises[siteId];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if local_mobile was added to the site.
|
||||
*
|
||||
* @param site Site to check.
|
||||
* @return Promise resolved if no action needed.
|
||||
*/
|
||||
protected async checkSiteLocalMobile(site: CoreSite): Promise<void> {
|
||||
try {
|
||||
// Check if local_mobile was installed in Moodle.
|
||||
await site.checkIfLocalMobileInstalledAndNotUsed();
|
||||
} catch {
|
||||
// Not added, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
// Local mobile was added. Throw invalid session to force reconnect and create a new token.
|
||||
CoreEvents.trigger(CoreEvents.SESSION_EXPIRED, {}, site.getId());
|
||||
|
||||
throw Translate.instance.instant('core.lostconnection');
|
||||
}
|
||||
|
||||
/**
|
||||
* Init Settings related to DOM.
|
||||
*/
|
||||
async initDomSettings(): Promise<void> {
|
||||
// Set the font size based on user preference.
|
||||
const fontSize = await CoreConfig.instance.get(CoreConstants.SETTINGS_FONT_SIZE, CoreConstants.CONFIG.font_sizes[0]);
|
||||
this.setFontSize(fontSize);
|
||||
|
||||
this.initColorScheme();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the color scheme.
|
||||
*/
|
||||
async initColorScheme(): Promise<void> {
|
||||
if (CoreConstants.CONFIG.forceColorScheme) {
|
||||
this.setColorScheme(CoreConstants.CONFIG.forceColorScheme);
|
||||
} else {
|
||||
const scheme = await CoreConfig.instance.get(CoreConstants.SETTINGS_COLOR_SCHEME, CoreColorScheme.LIGHT);
|
||||
this.setColorScheme(scheme);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if color scheme is disabled in a site.
|
||||
*
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with whether color scheme is disabled.
|
||||
*/
|
||||
async isColorSchemeDisabled(siteId?: string): Promise<boolean> {
|
||||
const site = await CoreSites.instance.getSite(siteId);
|
||||
|
||||
return this.isColorSchemeDisabledInSite(site);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if color scheme is disabled in a site.
|
||||
*
|
||||
* @param site Site instance. If not defined, current site.
|
||||
* @return Whether color scheme is disabled.
|
||||
*/
|
||||
isColorSchemeDisabledInSite(site?: CoreSite): boolean {
|
||||
site = site || CoreSites.instance.getCurrentSite();
|
||||
|
||||
return site ? site.isFeatureDisabled('NoDelegate_DarkMode') : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set document default font size.
|
||||
*
|
||||
* @param fontSize Font size in percentage.
|
||||
*/
|
||||
setFontSize(fontSize: number): void {
|
||||
// @todo Since zoom is deprecated and fontSize is not working, we should do some research here.
|
||||
document.documentElement.style.zoom = fontSize + '%';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set body color scheme.
|
||||
*
|
||||
* @param colorScheme Name of the color scheme.
|
||||
*/
|
||||
setColorScheme(colorScheme: CoreColorScheme): void {
|
||||
if (colorScheme == CoreColorScheme.AUTO && this.prefersDark) {
|
||||
// Listen for changes to the prefers-color-scheme media query.
|
||||
this.prefersDark.addEventListener('change', this.toggleDarkModeListener);
|
||||
|
||||
this.toggleDarkMode(this.prefersDark.matches);
|
||||
} else {
|
||||
// Stop listening to changes.
|
||||
this.prefersDark?.removeEventListener('change', this.toggleDarkModeListener);
|
||||
|
||||
this.toggleDarkMode(colorScheme == CoreColorScheme.DARK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener function to toggle dark mode.
|
||||
*
|
||||
* @param e Event object.
|
||||
*/
|
||||
protected toggleDarkModeListener = (e: MediaQueryListEvent): void => {
|
||||
document.body.classList.toggle('dark', e.matches);
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggles dark mode based on enabled boolean.
|
||||
*
|
||||
* @param enable True to enable dark mode, false to disable.
|
||||
*/
|
||||
protected toggleDarkMode(enable: boolean = false): void {
|
||||
document.body.classList.toggle('dark', enable);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class CoreSettingsHelper extends makeSingleton(CoreSettingsHelperProvider) {}
|
|
@ -16,6 +16,7 @@ import { NgModule } from '@angular/core';
|
|||
import { Routes } from '@angular/router';
|
||||
|
||||
import { CoreMainMenuRoutingModule } from '@core/mainmenu/mainmenu-routing.module';
|
||||
import { CoreSettingsHelperProvider } from './services/settings.helper';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
|
@ -25,7 +26,21 @@ const routes: Routes = [
|
|||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [CoreMainMenuRoutingModule.forChild(routes)],
|
||||
exports: [CoreMainMenuRoutingModule],
|
||||
imports: [
|
||||
CoreMainMenuRoutingModule.forChild(routes),
|
||||
],
|
||||
exports: [
|
||||
CoreMainMenuRoutingModule,
|
||||
],
|
||||
providers: [
|
||||
CoreSettingsHelperProvider,
|
||||
],
|
||||
})
|
||||
export class CoreSettingsInitModule {}
|
||||
export class CoreSettingsInitModule {
|
||||
|
||||
constructor(settingsHelper: CoreSettingsHelperProvider) {
|
||||
// @todo
|
||||
// settingsHelper.initDomSettings();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,10 @@ const routes: Routes = [
|
|||
path: 'about',
|
||||
loadChildren: () => import('./pages/about/about.page.module').then( m => m.CoreSettingsAboutPageModule),
|
||||
},
|
||||
{
|
||||
path: 'general',
|
||||
loadChildren: () => import('./pages/general/general.page.module').then( m => m.CoreSettingsGeneralPageModule),
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
loadChildren: () => import('./pages/app/app.page.module').then( m => m.CoreSettingsAppPageModule),
|
||||
|
|
|
@ -101,6 +101,7 @@ export class CoreAppProvider {
|
|||
});
|
||||
});
|
||||
|
||||
// @todo
|
||||
// this.platform.registerBackButtonAction(() => {
|
||||
// this.backButtonAction();
|
||||
// }, 100);
|
||||
|
@ -157,7 +158,7 @@ export class CoreAppProvider {
|
|||
async createTablesFromSchema(schema: CoreAppSchema): Promise<void> {
|
||||
this.logger.debug(`Apply schema to app DB: ${schema.name}`);
|
||||
|
||||
let oldVersion;
|
||||
let oldVersion: number;
|
||||
|
||||
try {
|
||||
// Wait for the schema versions table to be created.
|
||||
|
|
|
@ -834,17 +834,14 @@ export class CoreDomUtilsProvider {
|
|||
* @return Promise resolved with boolean: true if enabled, false otherwise.
|
||||
*/
|
||||
isRichTextEditorEnabled(): Promise<boolean> {
|
||||
if (this.isRichTextEditorSupported()) {
|
||||
return CoreConfig.instance.get(CoreConstants.SETTINGS_RICH_TEXT_EDITOR, true).then((enabled) => !!enabled);
|
||||
}
|
||||
|
||||
return Promise.resolve(false);
|
||||
return CoreConfig.instance.get(CoreConstants.SETTINGS_RICH_TEXT_EDITOR, true).then((enabled) => !!enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if rich text editor is supported in the platform.
|
||||
*
|
||||
* @return Whether it's supported.
|
||||
* @deprecated since 3.9.5
|
||||
*/
|
||||
isRichTextEditorSupported(): boolean {
|
||||
return true;
|
||||
|
|
|
@ -127,3 +127,21 @@ ion-avatar ion-img, ion-avatar img {
|
|||
background-color: --var(--gray-light);
|
||||
}
|
||||
|
||||
// Action sheet.
|
||||
.md ion-action-sheet {
|
||||
.action-sheet-group-cancel {
|
||||
-webkit-filter: drop-shadow(0px 3px 3px rgba(var(--action-sheet-shadow-color)));
|
||||
filter: drop-shadow(0px 3px 3px rgba(var(--action-sheet-shadow-color)));
|
||||
}
|
||||
|
||||
.action-sheet-title {
|
||||
border-bottom: 1px solid var(--title-border-color);
|
||||
}
|
||||
}
|
||||
|
||||
.ios ion-action-sheet {
|
||||
.action-sheet-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -105,6 +105,21 @@
|
|||
--background: var(--custom-toolbar-background, var(--ion-color-primary));
|
||||
}
|
||||
|
||||
ion-action-sheet {
|
||||
--action-sheet-shadow-color: var(--custom--action-sheet-shadow-color, 0, 0, 0, 1);
|
||||
--button-color-selected: var(--custom--action-sheet-selected-color, var(--core-color));
|
||||
--title-border-color: var(--custom-title-border-color, var(--gray));
|
||||
|
||||
@media (min-height: 500px) {
|
||||
--max-height: 50%;
|
||||
--height: 100%;
|
||||
}
|
||||
|
||||
.action-sheet-cancel {
|
||||
--button-color: var(--ion-color-danger);
|
||||
}
|
||||
}
|
||||
|
||||
core-tabs {
|
||||
--background: var(--custom-tabs-background, var(--white));
|
||||
ion-slide {
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
import { CoreColorScheme } from '@core/settings/services/settings.helper';
|
||||
import { CoreSitesDemoSiteData } from '@services/sites';
|
||||
|
||||
declare global {
|
||||
|
@ -59,7 +60,7 @@ declare global {
|
|||
statusbarlighttextremotetheme: boolean;
|
||||
enableanalytics: boolean;
|
||||
enableonboarding: boolean;
|
||||
forceColorScheme: string;
|
||||
forceColorScheme: CoreColorScheme;
|
||||
forceLoginLogo: boolean;
|
||||
ioswebviewscheme: string;
|
||||
appstores: Record<string, string>;
|
||||
|
|
Loading…
Reference in New Issue