diff --git a/src/addons/block/myoverview/components/myoverview/addon-block-myoverview.html b/src/addons/block/myoverview/components/myoverview/addon-block-myoverview.html index b3e3a4caf..f91a08355 100644 --- a/src/addons/block/myoverview/components/myoverview/addon-block-myoverview.html +++ b/src/addons/block/myoverview/components/myoverview/addon-block-myoverview.html @@ -34,10 +34,9 @@ -
+
- + {{ 'addon.block_myoverview.allincludinghidden' | translate }} @@ -66,7 +65,7 @@ {{ 'addon.block_myoverview.hiddencourses' | translate }} - +
diff --git a/src/addons/block/myoverview/components/myoverview/myoverview.ts b/src/addons/block/myoverview/components/myoverview/myoverview.ts index 630d60a28..c2c4fe7c4 100644 --- a/src/addons/block/myoverview/components/myoverview/myoverview.ts +++ b/src/addons/block/myoverview/components/myoverview/myoverview.ts @@ -414,8 +414,11 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem /** * The selected courses filter have changed. + * + * @param filter New filter */ - selectedChanged(): void { + selectedChanged(filter: string): void { + this.selectedFilter = filter; this.setCourseFilter(this.selectedFilter); } diff --git a/src/addons/block/timeline/components/timeline/addon-block-timeline.html b/src/addons/block/timeline/components/timeline/addon-block-timeline.html index 188b94023..2249fac9e 100644 --- a/src/addons/block/timeline/components/timeline/addon-block-timeline.html +++ b/src/addons/block/timeline/components/timeline/addon-block-timeline.html @@ -10,9 +10,8 @@ -
- +
+ {{ 'core.all' | translate }} {{ 'addon.block_timeline.overdue' | translate }} {{ 'addon.block_timeline.duedate' | translate }} @@ -20,7 +19,7 @@ {{ 'addon.block_timeline.next30days' | translate }} {{ 'addon.block_timeline.next3months' | translate }} {{ 'addon.block_timeline.next6months' | translate }} - +
-
- - {{ selectedSortOrder.label | translate }} -
-
+
+ +
sortOrder.value === value) || this.sortOrders[0]; + this.sortOrderSelectorModalOptions.componentProps!.selected = this.selectedSortOrder.value; } /** @@ -621,31 +630,18 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom } /** - * Display the sort order selector modal. + * Changes the sort order. + * + * @param sortOrder Sort order new data. */ - async showSortOrderSelector(): Promise { - if (!this.sortingAvailable) { - return; - } - - const modal = await ModalController.create({ - component: AddonModForumSortOrderSelectorComponent, - componentProps: { - sortOrders: this.sortOrders, - selected: this.selectedSortOrder!.value, - }, - }); - - await modal.present(); - - const result = await modal.onDidDismiss(); - - if (result.data && result.data.value != this.selectedSortOrder?.value) { - this.selectedSortOrder = result.data; + async setSortOrder(sortOrder: AddonModForumSortOrder): Promise { + if (sortOrder.value != this.selectedSortOrder?.value) { + this.selectedSortOrder = sortOrder; + this.sortOrderSelectorModalOptions.componentProps!.selected = this.selectedSortOrder.value; this.page = 0; try { - await CoreUser.setUserPreference(AddonModForumProvider.PREFERENCE_SORTORDER, result.data.value.toFixed(0)); + await CoreUser.setUserPreference(AddonModForumProvider.PREFERENCE_SORTORDER, sortOrder.value.toFixed(0)); await this.showLoadingAndFetch(); } catch (error) { CoreDomUtils.showErrorModalDefault(error, 'Error updating preference.'); @@ -653,6 +649,17 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom } } + /** + * Display the sort order selector modal. + */ + async showSortOrderSelector(): Promise { + const modalData = await CoreDomUtils.openModal(this.sortOrderSelectorModalOptions); + + if (modalData) { + this.setSortOrder(modalData); + } + } + /** * Show the context menu. * diff --git a/src/addons/mod/forum/components/sort-order-selector/sort-order-selector.html b/src/addons/mod/forum/components/sort-order-selector/sort-order-selector.html index 9be7f44ab..fa99df96d 100644 --- a/src/addons/mod/forum/components/sort-order-selector/sort-order-selector.html +++ b/src/addons/mod/forum/components/sort-order-selector/sort-order-selector.html @@ -1,6 +1,6 @@ - {{ 'core.sort' | translate }} + {{ 'core.sort' | translate }} @@ -9,7 +9,7 @@ - +

{{user!.fullname}}

-
- - {{ 'addon.notes.sitenotes' | translate }} - {{ 'addon.notes.coursenotes' | translate }} - {{ 'addon.notes.personalnotes' | translate }} - -
+ + {{ 'addon.notes.sitenotes' | translate }} + {{ 'addon.notes.coursenotes' | translate }} + {{ 'addon.notes.personalnotes' | translate }} + diff --git a/src/addons/notes/pages/list/list.page.ts b/src/addons/notes/pages/list/list.page.ts index bd3528a1a..e4385b156 100644 --- a/src/addons/notes/pages/list/list.page.ts +++ b/src/addons/notes/pages/list/list.page.ts @@ -157,8 +157,11 @@ export class AddonNotesListPage implements OnInit, OnDestroy { /** * Function called when the type has changed. + * + * @param type New type. */ - async typeChanged(): Promise { + async typeChanged(type: string): Promise { + this.type = type; this.notesLoaded = false; this.refreshIcon = CoreConstants.ICON_LOADING; this.syncIcon = CoreConstants.ICON_LOADING; @@ -199,8 +202,7 @@ export class AddonNotesListPage implements OnInit, OnDestroy { this.refreshNotes(false); } else if (result.data.type && result.data.type != this.type) { - this.type = result.data.type; - this.typeChanged(); + this.typeChanged(result.data.type); } } } diff --git a/src/addons/notifications/pages/settings/settings.html b/src/addons/notifications/pages/settings/settings.html index e8f225b99..b343928fc 100644 --- a/src/addons/notifications/pages/settings/settings.html +++ b/src/addons/notifications/pages/settings/settings.html @@ -41,13 +41,12 @@ - + {{ processor.displayname }} - + diff --git a/src/addons/privatefiles/pages/index/index.html b/src/addons/privatefiles/pages/index/index.html index 8d136e900..c35695fcb 100644 --- a/src/addons/privatefiles/pages/index/index.html +++ b/src/addons/privatefiles/pages/index/index.html @@ -14,12 +14,10 @@ -
- - {{ 'addon.privatefiles.privatefiles' | translate }} - {{ 'addon.privatefiles.sitefiles' | translate }} - -
+ + {{ 'addon.privatefiles.privatefiles' | translate }} + {{ 'addon.privatefiles.sitefiles' | translate }} + diff --git a/src/addons/privatefiles/pages/index/index.ts b/src/addons/privatefiles/pages/index/index.ts index 8c9933dd0..b20ad8ad7 100644 --- a/src/addons/privatefiles/pages/index/index.ts +++ b/src/addons/privatefiles/pages/index/index.ts @@ -99,7 +99,7 @@ export class AddonPrivateFilesIndexPage implements OnInit, OnDestroy { } if (this.root) { - this.rootChanged(); + this.rootChanged(this.root); } else { this.filesLoaded = true; } @@ -127,8 +127,12 @@ export class AddonPrivateFilesIndexPage implements OnInit, OnDestroy { /** * Function called when the root has changed. + * + * @param root New root. */ - rootChanged(): void { + rootChanged(root: 'my' | 'site'): void { + this.root = root; + this.filesLoaded = false; this.component = this.root == 'my' ? AddonPrivateFilesProvider.PRIVATE_FILES_COMPONENT : AddonPrivateFilesProvider.SITE_FILES_COMPONENT; diff --git a/src/core/components/combobox/combobox.scss b/src/core/components/combobox/combobox.scss new file mode 100644 index 000000000..19a8eb6fc --- /dev/null +++ b/src/core/components/combobox/combobox.scss @@ -0,0 +1,129 @@ +@import "~theme/globals"; + +:host { + ion-select, + ion-button { + --icon-margin: 0 8px; + --background: var(--core-combobox-background); + --background-hover: #000000; + --background-activated: #000000; + --background-focused: #000000; + --background-hover-opacity: .04; + + --color: var(--core-combobox-color); + --color-activated: var(--core-combobox-color); + --color-focused: currentcolor; + --color-hover: currentcolor; + + --padding-top: 10px; + --padding-end: 10px; + --padding-bottom: 10px; + --padding-start: 16px; + } +} + +:host-context(.md) { + --background-activated-opacity: 0; + --background-focused-opacity: .12; +} + +:host-context(.ios) { + --background-activated-opacity: .12; + --background-focused-opacity: .15; +} + +ion-select, +ion-button { + background: var(--background); + color: var(--color); + text-overflow: ellipsis; + white-space: nowrap; + min-height: 25px; + overflow: hidden; + margin: 8px; + box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .2), 0 2px 2px 0 rgba(0, 0, 0, .14), 0 1px 5px 0 rgba(0, 0, 0, .12); +} + +ion-select { + &::part(icon) { + margin: var(--icon-margin); + } + + &::after { + @include button-state(); + transition: var(--transition); + z-index: -1; + } + + &:hover::after { + color: var(--color-hover); + background: var(--background-hover); + opacity: var(--background-hover-opacity); + } + + &:focus::after, + &:focus-within::after { + color: var(--color-focused); + background: var(--background-focused); + opacity: var(--background-focused-opacity); + } +} + +ion-button { + --border-radius: 0; + flex: 1; + min-height: 45px; + + &::part(native) { + text-transform: none; + font-weight: 400; + font-size: 16px; + } + + .select-text { + margin-inline-end: auto; + overflow: hidden; + text-overflow: ellipsis; + } + .sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + } + + &.ion-activated { + --color: var(--color-activated); + } + + ion-icon { + margin: var(--icon-margin); + } + + .select-icon { + margin: var(--icon-margin); + width: 19px; + height: 19px; + position: relative; + opacity: 0.33; + + .select-icon-inner { + left: 5px; + top: 50%; + margin-top: -2px; + position: absolute; + width: 0px; + height: 0px; + color: currentcolor; + pointer-events: none; + border-top: 5px solid; + border-right: 5px solid transparent; + border-left: 5px solid transparent; + } + } +} diff --git a/src/core/components/combobox/combobox.ts b/src/core/components/combobox/combobox.ts new file mode 100644 index 000000000..068e5c60e --- /dev/null +++ b/src/core/components/combobox/combobox.ts @@ -0,0 +1,77 @@ +// (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, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; +import { Translate } from '@singletons'; +import { ModalOptions } from '@ionic/core'; +import { CoreDomUtils } from '@services/utils/dom'; + +/** + * Component that show a combo select button (combobox). + * + * @description + * + * Example using modal: + * + * + * selection + * + * + * Example using popover: + * + * + * 1 + * + */ +@Component({ + selector: 'core-combobox', + templateUrl: 'core-combobox.html', + styleUrls: ['combobox.scss'], + encapsulation: ViewEncapsulation.ShadowDom, +}) +export class CoreComboboxComponent { + + @Input() interface: 'popover' | 'modal' = 'popover'; + @Input() label = Translate.instant('core.show'); // Aria label. + @Input() disabled = false; + @Input() selection = ''; + @Output() onChange = new EventEmitter(); // Will emit an event the value changed. + + // Additional options when interface modal is selected. + @Input() icon?: string; // Icon for modal interface. + @Input() protected modalOptions?: ModalOptions; // Will emit an event the value changed. + @Input() listboxId = ''; + + expanded = false; + + async showModal(): Promise { + if (this.expanded || !this.modalOptions) { + return; + } + this.expanded = true; + + if (this.listboxId) { + this.modalOptions.id = this.listboxId; + } + + const data = await CoreDomUtils.openModal(this.modalOptions); + this.expanded = false; + + if (data) { + this.onChange.emit(data); + } + } + +} diff --git a/src/core/components/combobox/core-combobox.html b/src/core/components/combobox/core-combobox.html new file mode 100644 index 000000000..502d108ba --- /dev/null +++ b/src/core/components/combobox/core-combobox.html @@ -0,0 +1,33 @@ + + + + + + + {{ label }}: +
+ {{selection}} +
+ +
diff --git a/src/core/components/components.module.ts b/src/core/components/components.module.ts index ed321c8fa..f319a9ff2 100644 --- a/src/core/components/components.module.ts +++ b/src/core/components/components.module.ts @@ -55,6 +55,7 @@ import { CoreTabsComponent } from './tabs/tabs'; import { CoreTabsOutletComponent } from './tabs-outlet/tabs-outlet'; import { CoreTimerComponent } from './timer/timer'; import { CoreUserAvatarComponent } from './user-avatar/user-avatar'; +import { CoreComboboxComponent } from './combobox/combobox'; @NgModule({ declarations: [ @@ -92,6 +93,7 @@ import { CoreUserAvatarComponent } from './user-avatar/user-avatar'; CoreTabsOutletComponent, CoreTimerComponent, CoreUserAvatarComponent, + CoreComboboxComponent, ], imports: [ CommonModule, @@ -136,6 +138,7 @@ import { CoreUserAvatarComponent } from './user-avatar/user-avatar'; CoreTabsOutletComponent, CoreTimerComponent, CoreUserAvatarComponent, + CoreComboboxComponent, ], }) export class CoreComponentsModule {} diff --git a/src/core/features/course/components/format/core-course-format.html b/src/core/features/course/components/format/core-course-format.html index 374274d27..ba3bfd8a9 100644 --- a/src/core/features/course/components/format/core-course-format.html +++ b/src/core/features/course/components/format/core-course-format.html @@ -19,18 +19,15 @@
- - - {{ 'core.course.sections' | translate }}: - + + - {{ 'core.course.sections' | translate }} + {{ 'core.course.sections' | translate }} -
-
+
diff --git a/src/core/features/course/components/format/format.ts b/src/core/features/course/components/format/format.ts index 2187defbd..4f14cd84b 100644 --- a/src/core/features/course/components/format/format.ts +++ b/src/core/features/course/components/format/format.ts @@ -27,7 +27,7 @@ import { ViewChild, ElementRef, } from '@angular/core'; - +import { ModalOptions } from '@ionic/core'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component'; @@ -49,7 +49,6 @@ import { IonContent, IonRefresher } from '@ionic/angular'; import { CoreUtils } from '@services/utils/utils'; import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; import { CoreBlockCourseBlocksComponent } from '@features/block/components/course-blocks/course-blocks'; -import { ModalController } from '@singletons'; import { CoreCourseSectionSelectorComponent } from '../section-selector/section-selector'; /** @@ -104,6 +103,9 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { hasSeveralSections?: boolean; imageThumb?: string; progress?: number; + sectionSelectorModalOptions: ModalOptions = { + component: CoreCourseSectionSelectorComponent, + }; protected sectionStatusObserver?: CoreEventObserver; protected selectTabObserver?: CoreEventObserver; @@ -122,6 +124,12 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { * Component being initialized. */ ngOnInit(): void { + + this.sectionSelectorModalOptions.componentProps = { + course: this.course, + sections: this.sections, + }; + // Listen for section status changes. this.sectionStatusObserver = CoreEvents.on( CoreEvents.SECTION_STATUS_CHANGED, @@ -194,6 +202,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { } if (changes.sections && this.sections) { + this.sectionSelectorModalOptions.componentProps!.sections = this.sections; this.treatSections(this.sections); } @@ -342,21 +351,11 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { this.sectionSelectorExpanded = true; - const modal = await ModalController.create({ - component: CoreCourseSectionSelectorComponent, - componentProps: { - course: this.course, - sections: this.sections, - selected: this.selectedSection, - }, - }); - await modal.present(); - - const result = await modal.onWillDismiss(); + const data = await CoreDomUtils.openModal(this.sectionSelectorModalOptions); this.sectionSelectorExpanded = false; - if (result?.data) { - this.sectionChanged(result?.data); + if (data) { + this.sectionChanged(data); } } @@ -368,6 +367,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { sectionChanged(newSection: CoreCourseSection): void { const previousValue = this.selectedSection; this.selectedSection = newSection; + this.sectionSelectorModalOptions.componentProps!.selected = this.selectedSection; this.data.section = this.selectedSection; if (newSection.id != this.allSectionsId) { diff --git a/src/core/features/course/components/section-selector/section-selector.html b/src/core/features/course/components/section-selector/section-selector.html index e31d1c9e9..a439b13e0 100644 --- a/src/core/features/course/components/section-selector/section-selector.html +++ b/src/core/features/course/components/section-selector/section-selector.html @@ -1,6 +1,6 @@ - {{ 'core.course.sections' | translate }} + {{ 'core.course.sections' | translate }} @@ -9,7 +9,7 @@ - + - + {{ 'core.tag.inalltagcoll' | translate }} {{ collection.name }} - + diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index cffd289dc..041867c16 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -15,7 +15,7 @@ import { Injectable, SimpleChange, ElementRef, KeyValueChanges } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; import { IonContent } from '@ionic/angular'; -import { AlertOptions, AlertButton, TextFieldTypes } from '@ionic/core'; +import { ModalOptions, AlertOptions, AlertButton, TextFieldTypes } from '@ionic/core'; import { Md5 } from 'ts-md5'; import { CoreApp } from '@services/app'; @@ -1681,6 +1681,26 @@ export class CoreDomUtilsProvider { }); } + /** + * Opens a Modal. + * + * @param opts Modal Options. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async openModal( + opts: ModalOptions, + ): Promise { + + const modal = await ModalController.create(opts); + + await modal.present(); + + const result = await modal.onWillDismiss(); + if (result?.data) { + return result?.data; + } + } + /** * View an image in a modal. * diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss index b031e6ec8..5dcf0cd2b 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -382,62 +382,6 @@ ion-select::part(text) { white-space: normal; } -ion-select.core-button-select, -.core-button-select { - --background: var(--core-button-select-background); - background: var(--background); - --color: var(--ion-color-primary); - color: var(--color); - text-overflow: ellipsis; - white-space: nowrap; - min-height: 45px; - overflow: hidden; - margin: 8px; - box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .2), 0 2px 2px 0 rgba(0, 0, 0, .14), 0 1px 5px 0 rgba(0, 0, 0, .12); - &::part(icon) { - margin: 0 8px; - } - .core-button-select-text { - margin-inline-end: auto; - } - - &.ion-activated { - --color: var(--ion-color-primary-contrast); - } - -} -ion-button.core-button-select { - --border-radius: 0; - &::part(native) { - text-transform: none; - font-weight: 400; - font-size: 16px; - } - ion-icon { - margin: 0 8px; - } - .select-icon { - width: 19px; - height: 19px; - position: relative; - opacity: 0.33; - - .select-icon-inner { - left: 5px; - top: 50%; - margin-top: -2px; - position: absolute; - width: 0px; - height: 0px; - color: currentcolor; - pointer-events: none; - border-top: 5px solid; - border-right: 5px solid transparent; - border-left: 5px solid transparent; - } - } -} - // File uploader. .action-sheet-button input.core-fileuploader-file-handler-input { position: absolute; diff --git a/src/theme/theme.dark.scss b/src/theme/theme.dark.scss index 37126c70e..096f833ae 100644 --- a/src/theme/theme.dark.scss +++ b/src/theme/theme.dark.scss @@ -67,7 +67,7 @@ --color: var(--white); } - --core-button-select-background: var(--custom-button-select-background, #3a3a3a); + --core-combobox-background: var(--custom-combobox-background, #3a3a3a); --core-login-background: var(--custom-login-background, #3a3a3a); --core-login-text-color: var(--custom-login-text-color, white); diff --git a/src/theme/theme.light.scss b/src/theme/theme.light.scss index dc8e0a9ee..165cb58c7 100644 --- a/src/theme/theme.light.scss +++ b/src/theme/theme.light.scss @@ -158,7 +158,8 @@ --color: inherit; } - --core-button-select-background: var(--custom-button-select-background, var(--ion-color-primary-contrast)); + --core-combobox-background: var(--custom-combobox-background, var(--ion-item-background)); + --core-combobox-color: var(--custom-combobox-color, var(--ion-color-primary)); --selected-item-color: var(--custom-selected-item-color, var(--core-color)); --selected-item-border-width: var(--custom-selected-item-border-width, 5px);