Merge pull request #3137 from crazyserver/MOBILE-3833

Mobile 3833
main
Dani Palou 2022-02-24 10:42:52 +01:00 committed by GitHub
commit 56a3287aff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 300 additions and 289 deletions

View File

@ -0,0 +1,13 @@
:host {
--mod-icon-filter: brightness(0);
core-mod-icon {
background: transparent;
margin: 0;
--filter: var(--mod-icon-filter);
}
}
:host-context(body.dark) {
--mod-icon-filter: brightness(0) invert(1);
}

View File

@ -28,6 +28,7 @@ import { CoreNavigator } from '@services/navigator';
@Component({ @Component({
selector: 'addon-block-activitymodules', selector: 'addon-block-activitymodules',
templateUrl: 'addon-block-activitymodules.html', templateUrl: 'addon-block-activitymodules.html',
styleUrls: ['activitymodules.scss'],
}) })
export class AddonBlockActivityModulesComponent extends CoreBlockBaseComponent implements OnInit { export class AddonBlockActivityModulesComponent extends CoreBlockBaseComponent implements OnInit {
@ -96,16 +97,13 @@ export class AddonBlockActivityModulesComponent extends CoreBlockBaseComponent i
// Sort the modnames alphabetically. // Sort the modnames alphabetically.
modFullNames = CoreUtils.sortValues(modFullNames); modFullNames = CoreUtils.sortValues(modFullNames);
for (const modName in modFullNames) { for (const modName in modFullNames) {
let icon: string; const iconModName = modName === 'resources' ? 'page' : modName;
if (modName === 'resources') { const icon = await CoreCourseModuleDelegate.getModuleIconSrc(iconModName, modIcons[iconModName]);
icon = await CoreCourse.getModuleIconSrc('page', modIcons['page']);
} else {
icon = await CoreCourseModuleDelegate.getModuleIconSrc(modName, modIcons[modName]);
}
this.entries.push({ this.entries.push({
icon: icon, icon,
iconModName,
name: modFullNames[modName], name: modFullNames[modName],
modName, modName,
}); });
@ -145,4 +143,5 @@ type AddonBlockActivityModuleEntry = {
icon: string; icon: string;
name: string; name: string;
modName: string; modName: string;
iconModName: string;
}; };

View File

@ -5,7 +5,7 @@
</ion-item-divider> </ion-item-divider>
<core-loading [hideUntil]="loaded" [fullscreen]="false"> <core-loading [hideUntil]="loaded" [fullscreen]="false">
<ion-item class="ion-text-wrap" *ngFor="let entry of entries" detail="true" button (click)="gotoCoureListModType(entry)"> <ion-item class="ion-text-wrap" *ngFor="let entry of entries" detail="true" button (click)="gotoCoureListModType(entry)">
<core-mod-icon slot="start" [modicon]="entry.icon" [modname]="entry.modName" [showAlt]="false"> <core-mod-icon slot="start" [modicon]="entry.icon" [modname]="entry.iconModName" [showAlt]="false">
</core-mod-icon> </core-mod-icon>
<ion-label>{{ entry.name }}</ion-label> <ion-label>{{ entry.name }}</ion-label>
</ion-item> </ion-item>

View File

@ -23,6 +23,7 @@ import { CoreConstants, ModPurpose } from '@/core/constants';
import { AddonModForumIndexComponent } from '../../components/index'; import { AddonModForumIndexComponent } from '../../components/index';
import { CoreModuleHandlerBase } from '@features/course/classes/module-base-handler'; import { CoreModuleHandlerBase } from '@features/course/classes/module-base-handler';
import { CoreCourseModuleData } from '@features/course/services/course-helper'; import { CoreCourseModuleData } from '@features/course/services/course-helper';
import { CoreIonicColorNames } from '@singletons/colors';
/** /**
* Handler to support forum modules. * Handler to support forum modules.
@ -58,7 +59,7 @@ export class AddonModForumModuleHandlerService extends CoreModuleHandlerBase imp
const data = await super.getData(module, courseId); const data = await super.getData(module, courseId);
if ('afterlink' in module && !!module.afterlink) { if ('afterlink' in module && !!module.afterlink) {
data.extraBadgeColor = ''; data.extraBadgeColor = undefined;
const match = />(\d+)[^<]+/.exec(module.afterlink); const match = />(\d+)[^<]+/.exec(module.afterlink);
data.extraBadge = match ? Translate.instant('addon.mod_forum.unreadpostsnumber', { $a : match[1] }) : ''; data.extraBadge = match ? Translate.instant('addon.mod_forum.unreadpostsnumber', { $a : match[1] }) : '';
} else { } else {
@ -112,7 +113,7 @@ export class AddonModForumModuleHandlerService extends CoreModuleHandlerBase imp
} }
data.extraBadge = Translate.instant('core.loading'); data.extraBadge = Translate.instant('core.loading');
data.extraBadgeColor = 'light'; data.extraBadgeColor = CoreIonicColorNames.DARK;
await CoreUtils.ignoreErrors(AddonModForum.invalidateForumData(courseId)); await CoreUtils.ignoreErrors(AddonModForum.invalidateForumData(courseId));
@ -120,7 +121,7 @@ export class AddonModForumModuleHandlerService extends CoreModuleHandlerBase imp
// Handle unread posts. // Handle unread posts.
const forum = await AddonModForum.getForum(courseId, moduleId, { siteId }); const forum = await AddonModForum.getForum(courseId, moduleId, { siteId });
data.extraBadgeColor = ''; data.extraBadgeColor = undefined;
data.extraBadge = forum.unreadpostscount data.extraBadge = forum.unreadpostscount
? Translate.instant( ? Translate.instant(
'addon.mod_forum.unreadpostsnumber', 'addon.mod_forum.unreadpostsnumber',
@ -129,7 +130,7 @@ export class AddonModForumModuleHandlerService extends CoreModuleHandlerBase imp
: ''; : '';
} catch { } catch {
// Ignore errors. // Ignore errors.
data.extraBadgeColor = ''; data.extraBadgeColor = undefined;
data.extraBadge = ''; data.extraBadge = '';
} }
} }

View File

@ -109,7 +109,7 @@
<ion-item class="ion-text-wrap addon-mod_h5pactivity-result-table-header"> <ion-item class="ion-text-wrap addon-mod_h5pactivity-result-table-header">
<ion-label> <ion-label>
<ion-row class="ion-align-items-center"> <ion-row class="ion-align-items-center">
<ion-col class="ion-text-center">{{ result.optionslabel }}</ion-col> <ion-col class="ion-text-start">{{ result.optionslabel }}</ion-col>
<ion-col class="ion-text-center">{{ result.correctlabel }}</ion-col> <ion-col class="ion-text-center">{{ result.correctlabel }}</ion-col>
<ion-col class="ion-text-center">{{ result.answerlabel }}</ion-col> <ion-col class="ion-text-center">{{ result.answerlabel }}</ion-col>
</ion-row> </ion-row>
@ -118,7 +118,7 @@
<ion-item *ngFor="let option of result.options" class="ion-text-wrap addon-mod_h5pactivity-result-table-row"> <ion-item *ngFor="let option of result.options" class="ion-text-wrap addon-mod_h5pactivity-result-table-row">
<ion-label> <ion-label>
<ion-row class="ion-align-items-center"> <ion-row class="ion-align-items-center">
<ion-col class="ion-text-center"> <ion-col class="ion-text-start">
<core-format-text [text]="option.description" [component]="component" [componentId]="cmId" <core-format-text [text]="option.description" [component]="component" [componentId]="cmId"
contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId"> contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId">
</core-format-text> </core-format-text>

View File

@ -20,6 +20,7 @@ import { CoreCourseModuleData } from '@features/course/services/course-helper';
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate'; import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate';
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
import { CoreFileHelper } from '@services/file-helper'; import { CoreFileHelper } from '@services/file-helper';
import { CoreSites } from '@services/sites';
import { CoreMimetypeUtils } from '@services/utils/mimetype'; import { CoreMimetypeUtils } from '@services/utils/mimetype';
import { CoreTextUtils } from '@services/utils/text'; import { CoreTextUtils } from '@services/utils/text';
import { CoreTimeUtils } from '@services/utils/time'; import { CoreTimeUtils } from '@services/utils/time';
@ -95,7 +96,14 @@ export class AddonModResourceModuleHandlerService extends CoreModuleHandlerBase
this.getResourceData(module, courseId, handlerData).then((extra) => { this.getResourceData(module, courseId, handlerData).then((extra) => {
handlerData.extraBadge = extra; handlerData.extraBadge = extra;
handlerData.extraBadgeColor = '';
return;
}).catch(() => {
// Ignore errors.
});
this.getIconSrc(module).then((icon) => {
handlerData.icon = icon;
return; return;
}).catch(() => { }).catch(() => {
@ -214,6 +222,36 @@ export class AddonModResourceModuleHandlerService extends CoreModuleHandlerBase
return extra.join(' '); return extra.join(' ');
} }
/**
* @inheritdoc
*/
async getIconSrc(module?: CoreCourseModuleData): Promise<string | undefined> {
if (!module) {
return;
}
if (CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('4.0')) {
return await CoreCourse.getModuleIconSrc(module.modname, module.modicon);
}
let mimetypeIcon = '';
if (module.contentsinfo) {
// No need to use the list of files.
const mimetype = module.contentsinfo.mimetypes[0];
if (mimetype) {
mimetypeIcon = CoreMimetypeUtils.getMimetypeIcon(mimetype);
}
} else if (module.contents && module.contents[0]) {
const files = module.contents;
const file = files[0];
mimetypeIcon = CoreMimetypeUtils.getFileIcon(file.filename || '');
}
return await CoreCourse.getModuleIconSrc(module.modname, module.modicon, mimetypeIcon);
}
/** /**
* @inheritdoc * @inheritdoc
*/ */

View File

@ -1,7 +1,6 @@
@import "~theme/globals"; @import "~theme/globals";
:host { :host {
--core-format-text-background-gradient-rgb: var(--background-rgb, #{$ion-item-background-rgb});
--course-storage-max-activity-height: 120px; --course-storage-max-activity-height: 120px;
ion-card.section ion-card-header { ion-card.section ion-card-header {
@ -23,8 +22,8 @@
min-height: var(--course-storage-max-activity-height); min-height: var(--course-storage-max-activity-height);
position: absolute; position: absolute;
@include position(0, 0, null, 0); @include position(0, 0, null, 0);
background: -webkit-linear-gradient(top, rgba(var(--core-format-text-background-gradient-rgb), 0) calc(100% - 30px), rgba(var(--core-format-text-background-gradient-rgb), 1) calc(100% - 20px)); background: -webkit-linear-gradient(top, rgba(var(--background-gradient-rgb), 0) calc(100% - 30px), rgba(var(--background-gradient-rgb), 1) calc(100% - 20px));
background: linear-gradient(to bottom, rgba(var(--core-format-text-background-gradient-rgb), 0) calc(100% - 30px), rgba(var(--core-format-text-background-gradient-rgb), 1) calc(100% - 20px)); background: linear-gradient(to bottom, rgba(var(--background-gradient-rgb), 0) calc(100% - 30px), rgba(var(--background-gradient-rgb), 1) calc(100% - 20px));
z-index: 6; z-index: 6;
pointer-events: none; pointer-events: none;
} }

View File

@ -1,62 +0,0 @@
// (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 { CoreCourseOptionsMenuHandler, CoreCourseOptionsMenuHandlerData } from '@features/course/services/course-options-delegate';
import { CoreCourseAnyCourseDataWithOptions } from '@features/courses/services/courses';
import { makeSingleton } from '@singletons';
/**
* Handler to inject an option into course menu so that user can get to the manage storage page.
*/
@Injectable( { providedIn: 'root' })
export class AddonStorageManagerCourseMenuHandlerService implements CoreCourseOptionsMenuHandler {
name = 'AddonStorageManager';
priority = 500;
isMenuHandler = true;
/**
* @inheritdoc
*/
async isEnabledForCourse(): Promise<boolean> {
return true;
}
/**
* @inheritdoc
*/
async isEnabled(): Promise<boolean> {
return true;
}
/**
* @inheritdoc
*/
getMenuDisplayData(
course: CoreCourseAnyCourseDataWithOptions,
): CoreCourseOptionsMenuHandlerData {
return {
icon: 'fas-cloud-download-alt',
title: 'addon.storagemanager.coursedownloads',
page: 'storage/' + course.id,
pageParams: {
title: course.displayname ?? course.fullname,
},
class: 'addon-storagemanager-coursemenu-handler',
};
}
}
export const AddonStorageManagerCourseMenuHandler = makeSingleton(AddonStorageManagerCourseMenuHandlerService);

View File

@ -14,12 +14,10 @@
import { NgModule, APP_INITIALIZER } from '@angular/core'; import { NgModule, APP_INITIALIZER } from '@angular/core';
import { Routes } from '@angular/router'; import { Routes } from '@angular/router';
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module'; import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module';
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
import { CoreSitePreferencesRoutingModule } from '@features/settings/pages/site/site-routing'; import { CoreSitePreferencesRoutingModule } from '@features/settings/pages/site/site-routing';
import { CoreSettingsDelegate } from '@features/settings/services/settings-delegate'; import { CoreSettingsDelegate } from '@features/settings/services/settings-delegate';
import { AddonStorageManagerCourseMenuHandler } from './services/handlers/course-menu';
import { AddonStorageManagerSettingsHandler } from './services/handlers/settings'; import { AddonStorageManagerSettingsHandler } from './services/handlers/settings';
const routes: Routes = [ const routes: Routes = [
@ -41,7 +39,6 @@ const routes: Routes = [
provide: APP_INITIALIZER, provide: APP_INITIALIZER,
multi: true, multi: true,
useValue: () => { useValue: () => {
CoreCourseOptionsDelegate.registerHandler(AddonStorageManagerCourseMenuHandler.instance);
CoreSettingsDelegate.registerHandler(AddonStorageManagerSettingsHandler.instance); CoreSettingsDelegate.registerHandler(AddonStorageManagerSettingsHandler.instance);
}, },
}, },

View File

@ -1,6 +1,6 @@
<div [class.core-loading-container]="loading || !safeUrl" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}"> <div [class.core-loading-container]="loading || !safeUrl" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}">
<core-navbar-buttons slot="end" append *ngIf="initialized && showFullscreenOnToolbar"> <core-navbar-buttons slot="end" prepend *ngIf="initialized && showFullscreenOnToolbar">
<ion-button fill="clear" (click)="toggleFullscreen()" <ion-button fill="clear" (click)="toggleFullscreen()"
[attr.aria-label]="(fullscreen ? 'core.disablefullscreen' : 'core.fullscreen') | translate"> [attr.aria-label]="(fullscreen ? 'core.disablefullscreen' : 'core.fullscreen') | translate">
<ion-icon *ngIf="!fullscreen" name="fas-expand" slot="icon-only" aria-hidden="true"></ion-icon> <ion-icon *ngIf="!fullscreen" name="fas-expand" slot="icon-only" aria-hidden="true"></ion-icon>

View File

@ -64,8 +64,8 @@ export class CoreCollapsibleItemDirective implements OnInit {
} }
this.maxHeight = this.maxHeight < defaultMaxHeight ? defaultMaxHeight : this.maxHeight; this.maxHeight = this.maxHeight < defaultMaxHeight ? defaultMaxHeight : this.maxHeight;
if (!this.maxHeight || (window.innerWidth > 576 && window.innerHeight > 576)) { if (!this.maxHeight) {
// Do not collapse on big screens. // Do not collapse.
return; return;
} }
@ -91,9 +91,6 @@ export class CoreCollapsibleItemDirective implements OnInit {
*/ */
protected calculateHeight(): void { protected calculateHeight(): void {
// @todo: Work on calculate this height better. // @todo: Work on calculate this height better.
if (!this.maxHeight) {
return;
}
// Remove max-height (if any) to calculate the real height. // Remove max-height (if any) to calculate the real height.
const initialMaxHeight = this.element.style.maxHeight; const initialMaxHeight = this.element.style.maxHeight;
@ -117,7 +114,11 @@ export class CoreCollapsibleItemDirective implements OnInit {
this.toggleExpandEnabled = enable; this.toggleExpandEnabled = enable;
this.element.classList.toggle('collapsible-enabled', enable); this.element.classList.toggle('collapsible-enabled', enable);
if (!enable || this.element.querySelector('ion-button.collapsible-toggle')) { if (!enable || this.element.querySelector('ion-button.collapsible-toggle')) {
this.element.style.maxHeight = !enable || this.expanded
? ''
: this.maxHeight + 'px';
return; return;
} }
@ -128,6 +129,7 @@ export class CoreCollapsibleItemDirective implements OnInit {
const toggleText = document.createElement('span'); const toggleText = document.createElement('span');
toggleText.classList.add('collapsible-toggle-text'); toggleText.classList.add('collapsible-toggle-text');
toggleText.classList.add('sr-only');
toggleButton.appendChild(toggleText); toggleButton.appendChild(toggleText);
const expandArrow = document.createElement('span'); const expandArrow = document.createElement('span');

View File

@ -279,23 +279,28 @@ export class CoreFormatTextDirective implements OnChanges {
*/ */
protected setExpandButtonEnabled(enable: boolean): void { protected setExpandButtonEnabled(enable: boolean): void {
this.toggleExpandEnabled = enable; this.toggleExpandEnabled = enable;
this.element.classList.toggle('core-text-formatted', enable); this.element.classList.toggle('collapsible-enabled', enable);
if (!enable || this.element.querySelector('ion-button.collapsible-toggle')) {
this.element.style.maxHeight = !enable || this.expanded
? ''
: this.maxHeight + 'px';
if (!enable || this.element.querySelector('ion-button.core-format-text-toggle')) {
return; return;
} }
// Add expand/collapse buttons // Add expand/collapse buttons
const toggleButton = document.createElement('ion-button'); const toggleButton = document.createElement('ion-button');
toggleButton.classList.add('core-format-text-toggle'); toggleButton.classList.add('collapsible-toggle');
toggleButton.setAttribute('fill', 'clear'); toggleButton.setAttribute('fill', 'clear');
const toggleText = document.createElement('span'); const toggleText = document.createElement('span');
toggleText.classList.add('core-format-text-toggle-text'); toggleText.classList.add('collapsible-toggle-text');
toggleText.classList.add('sr-only');
toggleButton.appendChild(toggleText); toggleButton.appendChild(toggleText);
const expandArrow = document.createElement('span'); const expandArrow = document.createElement('span');
expandArrow.classList.add('core-format-text-arrow'); expandArrow.classList.add('collapsible-toggle-arrow');
toggleButton.appendChild(expandArrow); toggleButton.appendChild(expandArrow);
this.element.appendChild(toggleButton); this.element.appendChild(toggleButton);
@ -313,12 +318,12 @@ export class CoreFormatTextDirective implements OnChanges {
expand = !this.expanded; expand = !this.expanded;
} }
this.expanded = expand; this.expanded = expand;
this.element.classList.toggle('core-text-format-expanded', expand); this.element.classList.toggle('collapsible-expanded', expand);
this.element.classList.toggle('core-text-format-collapsed', !expand); this.element.classList.toggle('collapsible-collapsed', !expand);
this.element.style.maxHeight = expand ? '' : this.maxHeight + 'px'; this.element.style.maxHeight = expand ? '' : this.maxHeight + 'px';
const toggleButton = this.element.querySelector('ion-button.core-format-text-toggle'); const toggleButton = this.element.querySelector('ion-button.collapsible-toggle');
const toggleText = toggleButton?.querySelector('.core-format-text-toggle-text'); const toggleText = toggleButton?.querySelector('.collapsible-toggle-text');
if (!toggleButton || !toggleText) { if (!toggleButton || !toggleText) {
return; return;
} }
@ -396,8 +401,7 @@ export class CoreFormatTextDirective implements OnChanges {
this.element.classList.add('core-disable-media-adapt'); this.element.classList.add('core-disable-media-adapt');
this.contentSpan.innerHTML = ''; // Remove current contents. this.contentSpan.innerHTML = ''; // Remove current contents.
if (this.maxHeight && result.div.innerHTML != '' && if (this.maxHeight && result.div.innerHTML != '') {
(window.innerWidth < 576 || window.innerHeight < 576)) { // Don't collapse in big screens.
// Move the children to the current element to be able to calculate the height. // Move the children to the current element to be able to calculate the height.
CoreDomUtils.moveChildren(result.div, this.contentSpan); CoreDomUtils.moveChildren(result.div, this.contentSpan);

View File

@ -33,16 +33,35 @@ ion-item.item.item-current {
--background: var(--primary); --background: var(--primary);
--color: var(--primary-contrast); --color: var(--primary-contrast);
border: 0; border: 0;
ion-badge {
border: 1px solid var(--primary-contrast);
}
::ng-deep ion-icon {
color: var(--primary-contrast);
}
} }
ion-icon.restricted { ion-icon.restricted {
font-size: 14px; font-size: 14px;
} }
ion-item.item.divider.section {
--padding-start: 0px;
&.item-current {
ion-badge {
border: 1px solid var(--primary-contrast);
}
ion-icon.expandable-status-icon {
color: var(--primary-contrast);
&:hover {
background: var(--primary-shade);
}
}
}
ion-icon.expandable-status-icon {
padding: 13px;
margin: 3px;
border-radius: 50%;
&:hover {
background: var(--gray-300);
}
}
}

View File

@ -5,21 +5,24 @@
<ng-container *ngIf="completion.istrackeduser"> <ng-container *ngIf="completion.istrackeduser">
<ng-container *ngFor="let rule of details"> <ng-container *ngFor="let rule of details">
<ion-chip *ngIf="rule.statuscomplete" color="success" role="listitem" [attr.aria-label]="rule.accessibleDescription"> <ion-chip *ngIf="rule.statuscomplete" color="success" role="listitem" [attr.aria-label]="rule.accessibleDescription"
class="completioninfo completion_complete">
<ion-icon name="fas-check" [attr.aria-label]="'core.course.completion_automatic:done' | translate "></ion-icon> <ion-icon name="fas-check" [attr.aria-label]="'core.course.completion_automatic:done' | translate "></ion-icon>
<ion-label> <ion-label>
{{ rule.rulevalue.description }} {{ rule.rulevalue.description }}
</ion-label> </ion-label>
</ion-chip> </ion-chip>
<ion-chip *ngIf="rule.statuscompletefail" color="danger" role="listitem" [attr.aria-label]="rule.accessibleDescription"> <ion-chip *ngIf="rule.statuscompletefail" color="danger" role="listitem" [attr.aria-label]="rule.accessibleDescription"
class="completioninfo completion_fail">
<ion-icon name="fas-times" [attr.aria-label]="'core.course.completion_automatic:failed' | translate "></ion-icon> <ion-icon name="fas-times" [attr.aria-label]="'core.course.completion_automatic:failed' | translate "></ion-icon>
<ion-label> <ion-label>
{{ rule.rulevalue.description }} {{ rule.rulevalue.description }}
</ion-label> </ion-label>
</ion-chip> </ion-chip>
<ion-chip *ngIf="rule.statusincomplete" color="dark" role="listitem" [attr.aria-label]="rule.accessibleDescription"> <ion-chip *ngIf="rule.statusincomplete" color="dark" role="listitem" [attr.aria-label]="rule.accessibleDescription"
class="completioninfo completion_incomplete">
<ion-icon name="fas-edit" [attr.aria-label]="'core.course.completion_automatic:todo' | translate "></ion-icon> <ion-icon name="fas-edit" [attr.aria-label]="'core.course.completion_automatic:todo' | translate "></ion-icon>
<ion-label> <ion-label>
{{ rule.rulevalue.description }} {{ rule.rulevalue.description }}
@ -29,7 +32,7 @@
</ng-container> </ng-container>
<ng-container *ngIf="!completion.istrackeduser"> <ng-container *ngIf="!completion.istrackeduser">
<ion-chip *ngFor="let rule of details" role="listitem"> <ion-chip *ngFor="let rule of details" role="listitem" class="core-module-completion-todo">
<ion-icon name="fas-edit" [attr.aria-label]="'core.course.completion_automatic:todo' | translate "></ion-icon> <ion-icon name="fas-edit" [attr.aria-label]="'core.course.completion_automatic:todo' | translate "></ion-icon>
<ion-label> <ion-label>
{{ rule.rulevalue.description }} {{ rule.rulevalue.description }}

View File

@ -1,12 +0,0 @@
:host {
.core-module-automatic-completion-conditions {
ion-badge {
font-weight: normal;
margin-right: 5px;
&[color="medium"] {
color: black;
}
}
}
}

View File

@ -36,7 +36,6 @@ import { Translate } from '@singletons';
@Component({ @Component({
selector: 'core-course-module-completion', selector: 'core-course-module-completion',
templateUrl: 'core-course-module-completion.html', templateUrl: 'core-course-module-completion.html',
styleUrls: ['module-completion.scss'],
}) })
export class CoreCourseModuleCompletionComponent extends CoreCourseModuleCompletionBaseComponent { export class CoreCourseModuleCompletionComponent extends CoreCourseModuleCompletionBaseComponent {

View File

@ -37,7 +37,7 @@
{{ 'core.course' | translate}} {{ 'core.course' | translate}}
</p> </p>
<p> <p>
<core-format-text [text]="course.displayname || course.fullname" contextLevel="course" [contextInstanceId]="courseId"> <core-format-text [text]="course.fullname" contextLevel="course" [contextInstanceId]="courseId">
</core-format-text> </core-format-text>
</p> </p>
</ion-label> </ion-label>
@ -58,7 +58,7 @@
<ion-item lines="full" class="ion-text-wrap"> <ion-item lines="full" class="ion-text-wrap">
<ion-label> <ion-label>
<h2> <h2>
<ion-icon name="fam-cloud-done" aria-hidden="true"></ion-icon> <ion-icon name="fas-cloud-download-alt" aria-hidden="true"></ion-icon>
{{ 'addon.storagemanager.downloads' | translate }} {{ 'addon.storagemanager.downloads' | translate }}
</h2> </h2>
</ion-label> </ion-label>
@ -81,7 +81,7 @@
</ion-item> </ion-item>
<ion-button fill="outline" expand="block" *ngIf="canPrefetch && displayOptions.displayPrefetch" class="ion-text-wrap" <ion-button fill="outline" expand="block" *ngIf="canPrefetch && displayOptions.displayPrefetch" class="ion-text-wrap"
(click)="prefetch()" color="primary" [disabled]="prefetchDisabled"> (click)="prefetch()" color="primary" [disabled]="prefetchDisabled">
<ion-icon *ngIf="!prefetchLoading" name="fam-cloud-done" slot="start" aria-hidden="true"></ion-icon> <ion-icon *ngIf="!prefetchLoading" name="fas-cloud-download-alt" slot="start" aria-hidden="true"></ion-icon>
<ion-spinner *ngIf="prefetchLoading" slot="start" aria-hidden="true"></ion-spinner> <ion-spinner *ngIf="prefetchLoading" slot="start" aria-hidden="true"></ion-spinner>
<ion-label> <ion-label>
{{ 'core.download' | translate }} {{ 'core.download' | translate }}

View File

@ -249,7 +249,13 @@ export class CoreCourseModuleSummaryComponent implements OnInit, OnDestroy {
* Fetch course. * Fetch course.
*/ */
protected async fetchCourse(): Promise<void> { protected async fetchCourse(): Promise<void> {
this.course = await CoreCourses.getUserCourse(this.courseId, true); // Fix that.
try {
this.course = await CoreCourses.getUserCourse(this.courseId, true);
} catch {
// The user is not enrolled in the course. Use getCourses to see if it's an admin/manager and can see the course.
this.course = await CoreCourses.getCourse(this.courseId);
}
} }
/** /**

View File

@ -71,7 +71,7 @@
</core-format-text> </core-format-text>
<!-- Module completion. Only auto conditions--> <!-- Module completion. Only auto conditions-->
<core-course-module-completion *ngIf="module.completiondata && module.uservisible" [completion]="module.completiondata" <core-course-module-completion *ngIf="autoCompletionTodo && module.uservisible" [completion]="module.completiondata"
[moduleName]="module.name" [moduleId]="module.id" [showCompletionConditions]="showCompletionConditions"> [moduleName]="module.name" [moduleId]="module.id" [showCompletionConditions]="showCompletionConditions">
</core-course-module-completion> </core-course-module-completion>

View File

@ -2,10 +2,14 @@
:host { :host {
--horizontal-margin: 10px; --horizontal-margin: 10px;
--vertical-margin: 10px;
ion-card { ion-card {
margin-left: var(--horizontal-margin); margin: var(--vertical-margin) var(--horizontal-margin);
margin-right: var(--horizontal-margin); }
ion-item {
--padding-start: 12px;
} }
ion-item.core-module-main-item { ion-item.core-module-main-item {
@ -84,4 +88,7 @@
@include margin-horizontal(null, 8px); @include margin-horizontal(null, 8px);
} }
.core-course-module-info ::ng-deep core-course-module-completion .core-module-automatic-completion-conditions .completioninfo.completion_complete {
display: none;
}
} }

View File

@ -20,7 +20,7 @@ import {
CoreCourseModuleCompletionData, CoreCourseModuleCompletionData,
CoreCourseSection, CoreCourseSection,
} from '@features/course/services/course-helper'; } from '@features/course/services/course-helper';
import { CoreCourse } from '@features/course/services/course'; import { CoreCourse, CoreCourseModuleCompletionStatus, CoreCourseModuleCompletionTracking } from '@features/course/services/course';
import { CoreCourseModuleDelegate, CoreCourseModuleHandlerButton } from '@features/course/services/module-delegate'; import { CoreCourseModuleDelegate, CoreCourseModuleHandlerButton } from '@features/course/services/module-delegate';
import { import {
CoreCourseModulePrefetchDelegate, CoreCourseModulePrefetchDelegate,
@ -55,6 +55,8 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
showManualCompletion = false; // Whether to show manual completion when completion conditions are disabled. showManualCompletion = false; // Whether to show manual completion when completion conditions are disabled.
prefetchStatusIcon = ''; // Module prefetch status icon. prefetchStatusIcon = ''; // Module prefetch status icon.
prefetchStatusText = ''; // Module prefetch status text. prefetchStatusText = ''; // Module prefetch status text.
autoCompletionTodo = false;
protected prefetchHandler?: CoreCourseModulePrefetchHandler; protected prefetchHandler?: CoreCourseModulePrefetchHandler;
protected moduleStatusObserver?: CoreEventObserver; protected moduleStatusObserver?: CoreEventObserver;
@ -73,10 +75,18 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
this.module.handlerData.a11yTitle = this.module.handlerData.a11yTitle ?? this.module.handlerData.title; this.module.handlerData.a11yTitle = this.module.handlerData.a11yTitle ?? this.module.handlerData.title;
const completionStatus = this.showCompletionConditions && this.module.completiondata?.isautomatic &&
this.module.completiondata.tracking == CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_AUTOMATIC
? this.module.completiondata.state
: undefined;
this.autoCompletionTodo = completionStatus == CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE ||
completionStatus == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_FAIL;
this.hasInfo = !!( this.hasInfo = !!(
this.module.description || this.module.description ||
(this.showActivityDates && this.module.dates && this.module.dates.length) || (this.showActivityDates && this.module.dates && this.module.dates.length) ||
(this.module.completiondata && this.showCompletionConditions && this.module.completiondata.isautomatic) || (this.autoCompletionTodo) ||
(this.module.visible === 0 && (!this.section || this.section.visible)) || (this.module.visible === 0 && (!this.section || this.section.visible)) ||
(this.module.visible !== 0 && this.module.isStealth) || (this.module.visible !== 0 && this.module.isStealth) ||
(this.module.availabilityinfo) (this.module.availabilityinfo)

View File

@ -1,4 +1,7 @@
<core-navbar-buttons slot="end"> <core-navbar-buttons slot="end" prepend>
<ion-button fill="clear" (click)="gotoCourseDownloads()" [attr.aria-label]="'addon.storagemanager.coursedownloads' | translate">
<ion-icon name="fas-cloud-download-alt" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons> </core-navbar-buttons>
<ion-content> <ion-content>
<ion-refresher slot="fixed" [disabled]="!dataLoaded || !displayRefresher" (ionRefresh)="doRefresh($event.target)"> <ion-refresher slot="fixed" [disabled]="!dataLoaded || !displayRefresher" (ionRefresh)="doRefresh($event.target)">

View File

@ -29,7 +29,6 @@ import {
} from '@features/course/services/course-helper'; } from '@features/course/services/course-helper';
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate'; import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
import { CoreCourseOptionsMenuHandlerToDisplay } from '@features/course/services/course-options-delegate';
import { CoreCourseSync, CoreCourseSyncProvider } from '@features/course/services/sync'; import { CoreCourseSync, CoreCourseSyncProvider } from '@features/course/services/sync';
import { CoreCourseFormatComponent } from '../../components/course-format/course-format'; import { CoreCourseFormatComponent } from '../../components/course-format/course-format';
import { import {
@ -54,7 +53,6 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
sections?: CoreCourseSection[]; sections?: CoreCourseSection[];
sectionId?: number; sectionId?: number;
sectionNumber?: number; sectionNumber?: number;
courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
dataLoaded = false; dataLoaded = false;
downloadCourseEnabled = false; downloadCourseEnabled = false;
moduleId?: number; moduleId?: number;
@ -368,8 +366,16 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
} }
} }
gotoCourseDownloads(): void {
CoreNavigator.navigateToSitePath(
`storage/${this.course.id}`,
{ params: { title: this.course.fullname } },
);
}
/** /**
* Page destroyed. * @inheritdoc
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
this.isDestroyed = true; this.isDestroyed = true;

View File

@ -26,14 +26,23 @@
<ng-container *ngIf="course"> <ng-container *ngIf="course">
<ion-item class="ion-text-wrap"> <ion-item class="ion-text-wrap">
<ion-label> <ion-label>
<p *ngIf="course.categoryname"> <p *ngIf="course.displayname && course.shortname && course.fullname != course.displayname"
<core-format-text [text]="course.categoryname" contextLevel="coursecat" [contextInstanceId]="course.categoryid"> class="core-course-shortname">
<core-format-text [text]="course.shortname" contextLevel="course" [contextInstanceId]="course.id">
</core-format-text> </core-format-text>
</p> </p>
<h2> <h2>
<span class="sr-only">{{ 'core.courses.aria:coursename' | translate }}</span>
<core-format-text [text]="course.fullname" contextLevel="course" [contextInstanceId]="course.id"> <core-format-text [text]="course.fullname" contextLevel="course" [contextInstanceId]="course.id">
</core-format-text> </core-format-text>
</h2> </h2>
<ion-chip color="brand" *ngIf="course.categoryname" class="core-course-category ion-text-nowrap">
<span class="sr-only">{{ 'core.courses.aria:coursecategory' | translate }}</span>
<ion-label>
<core-format-text [text]="course.categoryname" contextLevel="coursecat" [contextInstanceId]="course.categoryid">
</core-format-text>
</ion-label>
</ion-chip>
<div class="core-course-progress" *ngIf="progress !== undefined"> <div class="core-course-progress" *ngIf="progress !== undefined">
<core-progress-bar [progress]="progress" a11yText="core.course.aria:sectionprogress"> <core-progress-bar [progress]="progress" a11yText="core.course.aria:sectionprogress">

View File

@ -21,10 +21,6 @@
<img [src]="imageThumb" core-external-content alt="" /> <img [src]="imageThumb" core-external-content alt="" />
</ion-avatar> </ion-avatar>
<ion-label> <ion-label>
<p *ngIf="category">
<core-format-text [text]="category" contextLevel="coursecat" [contextInstanceId]="course!.categoryid">
</core-format-text>
</p>
<h1>{{ title }}</h1> <h1>{{ title }}</h1>
<div class="core-course-progress" *ngIf="progress !== undefined"> <div class="core-course-progress" *ngIf="progress !== undefined">
<core-progress-bar [progress]="progress" a11yText="core.course.aria:sectionprogress"> <core-progress-bar [progress]="progress" a11yText="core.course.aria:sectionprogress">

View File

@ -238,7 +238,6 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
// Get the title to display initially. // Get the title to display initially.
this.title = CoreCourseFormatDelegate.getCourseTitle(this.course); this.title = CoreCourseFormatDelegate.getCourseTitle(this.course);
this.category = 'categoryname' in this.course ? this.course.categoryname : '';
if ('overviewfiles' in this.course) { if ('overviewfiles' in this.course) {
this.imageThumb = this.course.overviewfiles?.[0]?.fileurl; this.imageThumb = this.course.overviewfiles?.[0]?.fileurl;

View File

@ -25,6 +25,7 @@ import { CoreSites } from '@services/sites';
import { makeSingleton } from '@singletons'; import { makeSingleton } from '@singletons';
import { CoreCourseModuleData } from './course-helper'; import { CoreCourseModuleData } from './course-helper';
import { CoreNavigationOptions } from '@services/navigator'; import { CoreNavigationOptions } from '@services/navigator';
import { CoreIonicColorNames } from '@singletons/colors';
/** /**
* Interface that all course module handlers must implement. * Interface that all course module handlers must implement.
@ -146,7 +147,7 @@ export interface CoreCourseModuleHandlerData {
/** /**
* The color of the extra badge. Default: primary. * The color of the extra badge. Default: primary.
*/ */
extraBadgeColor?: string; extraBadgeColor?: CoreIonicColorNames;
/** /**
* Whether to display a button to download/refresh the module if it's downloadable. * Whether to display a button to download/refresh the module if it's downloadable.

View File

@ -70,7 +70,8 @@
class="core-course-category core-course-additional-info ion-text-nowrap"> class="core-course-category core-course-additional-info ion-text-nowrap">
<span class="sr-only">{{ 'core.courses.aria:coursecategory' | translate }}</span> <span class="sr-only">{{ 'core.courses.aria:coursecategory' | translate }}</span>
<ion-label> <ion-label>
<core-format-text [text]="course.categoryname"></core-format-text> <core-format-text [text]="course.categoryname" contextLevel="coursecat" [contextInstanceId]="course.categoryid">
</core-format-text>
</ion-label> </ion-label>
</ion-chip> </ion-chip>
</div> </div>

View File

@ -34,6 +34,10 @@
margin-bottom: 16px; margin-bottom: 16px;
} }
form .core-username.ios {
--inner-border-width: 0 0 1px 0;
}
form .item, form .item,
form .item ion-label { form .item ion-label {
--background: var(--core-login-input-background); --background: var(--core-login-input-background);

View File

@ -15,7 +15,7 @@
<core-loading [hideUntil]="userLoaded"> <core-loading [hideUntil]="userLoaded">
<ion-list *ngIf="user"> <ion-list *ngIf="user">
<ion-item class="ion-text-center core-user-profile-maininfo ion-text-wrap" lines="full"> <ion-item class="ion-text-center core-user-profile-maininfo ion-text-wrap" lines="full">
<core-user-avatar [user]="user" [userId]="user.id" [linkProfile]="false" [checkOnline]="true"> <core-user-avatar [user]="user" [userId]="user.id" [linkProfile]="false" [checkOnline]="!canChangeProfilePicture">
<ion-button class="edit-avatar" *ngIf="canChangeProfilePicture" (click)="changeProfilePicture()" <ion-button class="edit-avatar" *ngIf="canChangeProfilePicture" (click)="changeProfilePicture()"
[attr.aria-label]="'core.user.newpicture' | translate" fill="clear" color="dark"> [attr.aria-label]="'core.user.newpicture' | translate" fill="clear" color="dark">
<ion-icon slot="icon-only" name="fas-pen" aria-hidden="true"></ion-icon> <ion-icon slot="icon-only" name="fas-pen" aria-hidden="true"></ion-icon>

View File

@ -4,13 +4,11 @@
core-format-text { core-format-text {
--core-format-text-background: var(--background, var(--ion-item-background)); --core-format-text-background: var(--background, var(--ion-item-background));
--core-format-text-background-gradient-rgb: var(--background-rgb, #{$ion-item-background-rgb});
--core-format-text-viewer-icon-background: rgba(255, 255, 255, .5); --core-format-text-viewer-icon-background: rgba(255, 255, 255, .5);
--core-format-text-loader-shine: 251,251,251; --core-format-text-loader-shine: 251,251,251;
} }
body.dark core-format-text { body.dark core-format-text {
--core-format-text-background-gradient-rgb: var(--background-rgb, #{$ion-item-background-dark-rgb});
--core-format-text-viewer-icon-background: rgba(0, 0, 0, .5); --core-format-text-viewer-icon-background: rgba(0, 0, 0, .5);
--core-format-text-loader-shine: 90,90,90; --core-format-text-loader-shine: 90,90,90;
} }
@ -51,12 +49,12 @@ core-format-text {
display: inline; display: inline;
} }
.core-format-text-toggle { .collapsible-toggle {
display: none !important; display: none !important;
} }
} }
.core-format-text-toggle { .collapsible-toggle {
display: none; display: none;
} }
@ -84,81 +82,21 @@ core-format-text {
} }
// This is to allow clicks in radio/checkbox content. // This is to allow clicks in radio/checkbox content.
&.core-text-formatted { &.collapsible-enabled {
cursor: pointer; cursor: pointer;
pointer-events: auto; pointer-events: auto;
.core-format-text-toggle { @include collapsible-item();
display: block;
position: absolute;
bottom: 0;
left: 0;
right: 0;
text-align: center;
z-index: 7;
text-transform: none;
text-align: end;
font-size: 14px;
background-color: var(--core-format-text-background);
color: var(--text-color);
margin: 0;
.core-format-text-arrow {
width: var(--a11y-min-target-size);
height: var(--a11y-min-target-size);
background-position: center;
background-repeat: no-repeat;
background-size: 14px 14px;
@include core-transition(transform, 500ms);
@include push-arrow-color(626262, true);
@include darkmode() {
@include push-arrow-color(ffffff, true);
}
}
}
&.core-text-format-collapsed {
overflow: hidden;
min-height: 50px;
.core-format-text-arrow {
transform: rotate(90deg);
}
&:before {
content: '';
height: 100%;
position: absolute;
@include position(null, 0, 0, 0);
background: -webkit-linear-gradient(top, rgba(var(--core-format-text-background-gradient-rgb), 0) calc(100% - 60px), rgba(var(--core-format-text-background-gradient-rgb), 1) calc(100% - 40px));
background: linear-gradient(to bottom, rgba(var(--core-format-text-background-gradient-rgb), 0) calc(100% - 60px), rgba(var(--core-format-text-background-gradient-rgb), 1) calc(100% - 40px));
z-index: 6;
}
}
&.core-text-format-expanded {
max-height: none !important;
padding-bottom: 50px; // So the Show less button can fit.
.core-format-text-arrow {
transform: rotate(-90deg);
}
}
} }
} }
@if ($core-format-text-never-shorten) { @if ($core-format-text-never-shorten) {
&[maxHeight], &[maxHeight],
&[ng-reflect-max-height] { &[ng-reflect-max-height] {
&.core-text-formatted.core-text-format-expanded { &.collapsible-enabled.collapsible-expanded {
max-height: none !important; max-height: none !important;
.core-format-text-toggle { .collapsible-toggle {
display: none !important; display: none !important;
} }

View File

@ -219,9 +219,84 @@
--horizontal-margin: 6px; --horizontal-margin: 6px;
} }
} }
} }
@mixin collapsible-item() {
--display-toggle: none;
.collapsible-toggle {
display: var(--display-toggle);
}
@include media-breakpoint-down(sm) {
&.collapsible-enabled {
position:relative;
--display-toggle: block;
.collapsible-toggle {
position: absolute;
@include position (null, 0, 0, null);
text-align: center;
z-index: 7;
text-transform: none;
font-size: 14px;
font-weight: normal;
background-color: var(--collapsible-toggle-background);
color: var(--collapsible-toggle-text);
min-height: var(--a11y-min-target-size);
min-width: var(--a11y-min-target-size);
--border-radius: var(--huge-radius);
border-radius: var(--border-radius);
--padding-start: 0px;
--padding-end: 0px;
margin: 0px;
.collapsible-toggle-arrow {
width: var(--a11y-min-target-size);
height: var(--a11y-min-target-size);
background-position: center;
background-repeat: no-repeat;
background-size: 14px 14px;
@include core-transition(transform, 500ms);
@include push-arrow-color(626262, true);
@include darkmode() {
@include push-arrow-color(ffffff, true);
}
}
}
&.collapsible-collapsed {
overflow: hidden;
min-height: calc(var(--collapsible-min-button-height) + 12px);
.collapsible-toggle-arrow {
transform: rotate(90deg);
}
&:before {
content: '';
height: 100%;
position: absolute;
@include position(null, 0, 0, 0);
background: -webkit-linear-gradient(top, rgba(var(--background-gradient-rgb), 0) calc(100% - 56px), rgba(var(--background-gradient-rgb), 1) calc(100% - 5px));
background: linear-gradient(to bottom, rgba(var(--background-gradient-rgb), 0) calc(100% - 56px), rgba(var(--background-gradient-rgb), 1) calc(100% - 5px));
z-index: 6;
}
}
&.collapsible-expanded {
max-height: none !important;
padding-bottom: var(--collapsible-min-button-height); // So the Show less button can fit.
.collapsible-toggle-arrow {
transform: rotate(-90deg);
}
}
}
}
}
// Color mixins. // Color mixins.
@function get_brightness($color) { @function get_brightness($color) {

View File

@ -164,6 +164,9 @@ ion-header {
--ion-toolbar-color: var(--core-header-toolbar-color); --ion-toolbar-color: var(--core-header-toolbar-color);
--border-radius: var(--huge-radius); --border-radius: var(--huge-radius);
} }
ion-back-button::part(text) {
display: none;
}
.button.button-clear, .button.button-clear,
.button.button-solid { .button.button-solid {
@ -813,6 +816,7 @@ ion-card {
border-color: var(--border-color); border-color: var(--border-color);
box-shadow: var(--box-shadow); box-shadow: var(--box-shadow);
border-radius: var(--border-radius); border-radius: var(--border-radius);
margin: var(--ion-card-vertical-margin) var(--ion-card-horizontal-margin);
ion-item:only-child { ion-item:only-child {
--inner-border-width: 0px; --inner-border-width: 0px;
@ -983,6 +987,12 @@ ion-chip {
min-height: 24px; min-height: 24px;
height: auto; height: auto;
// Chips are not currently clickable.
&.ion-activatable {
cursor: auto;
pointer-events: none;
}
&.ion-color { &.ion-color {
background: var(--ion-color-tint); background: var(--ion-color-tint);
&.chip-outline { &.chip-outline {
@ -1145,6 +1155,10 @@ ion-item.item-lines-inset {
--border-width: 0px; --border-width: 0px;
} }
ion-item.item-input.ios {
--inner-border-width: 0 0 1px 0;
}
// Fake item. // Fake item.
div.fake-ion-item { div.fake-ion-item {
position: relative; position: relative;
@ -1425,77 +1439,7 @@ ion-grid.core-no-grid > ion-row {
} }
[collapsible-item] { [collapsible-item] {
--collapsible-display-toggle: none; @include collapsible-item();
--collapsible-toggle-background: var(--ion-item-background);
--collapsible-min-button-height: 44px;
.collapsible-toggle {
display: var(--collapsible-display-toggle);
}
&.collapsible-enabled {
--collapsible-display-toggle: block;
.collapsible-toggle {
display: var(--collapsible-display-toggle);
position: absolute;
bottom: 0;
left: 0;
right: 0;
text-align: center;
z-index: 7;
text-transform: none;
text-align: end;
font-size: 14px;
background-color: var(--collapsible-toggle-background);
color: var(--text-color);
margin: 0;
.collapsible-toggle-arrow {
width: var(--a11y-min-target-size);
height: var(--a11y-min-target-size);
background-position: center;
background-repeat: no-repeat;
background-size: 14px 14px;
@include core-transition(transform, 500ms);
@include push-arrow-color(626262, true);
@include darkmode() {
@include push-arrow-color(ffffff, true);
}
}
}
&.collapsible-collapsed {
overflow: hidden;
min-height: calc(var(--collapsible-min-button-height) + 12);
.collapsible-toggle-arrow {
transform: rotate(90deg);
}
&:before {
content: '';
height: 100%;
position: absolute;
@include position(null, 0, 0, 0);
background: -webkit-linear-gradient(top, rgba(var(--core-format-text-background-gradient-rgb), 0) calc(100% - 60px), rgba(var(--core-format-text-background-gradient-rgb), 1) calc(100% - 40px));
background: linear-gradient(to bottom, rgba(var(--core-format-text-background-gradient-rgb), 0) calc(100% - 60px), rgba(var(--core-format-text-background-gradient-rgb), 1) calc(100% - 40px));
z-index: 6;
}
}
&.collapsible-expanded {
max-height: none !important;
padding-bottom: var(--collapsible-min-button-height); // So the Show less button can fit.
.collapsible-toggle-arrow {
transform: rotate(-90deg);
}
}
}
} }
ion-header.no-title { ion-header.no-title {

View File

@ -97,6 +97,10 @@
--core-combobox-color: var(--text-color); --core-combobox-color: var(--text-color);
--core-combobox-border-color: var(--core-input-stroke); --core-combobox-border-color: var(--core-input-stroke);
--collapsible-toggle-background: var(--light);
--background-gradient-rgb: #{$ion-item-background-dark-rgb};
--core-login-background: var(--gray-900); --core-login-background: var(--gray-900);
--core-login-text-color: var(--white); --core-login-text-color: var(--white);
--core-login-input-background: var(--core-login-background); --core-login-input-background: var(--core-login-background);

View File

@ -88,6 +88,8 @@
--subdued-text-color: var(--gray-700); --subdued-text-color: var(--gray-700);
--ion-card-color: var(--text-color); --ion-card-color: var(--text-color);
--ion-card-vertical-margin: 10px;
--ion-card-horizontal-margin: 10px;
ion-card { ion-card {
--border-width: 1px; --border-width: 1px;
--border-style: solid; --border-style: solid;
@ -286,6 +288,12 @@
--selected-item-color: var(--primary); --selected-item-color: var(--primary);
--selected-item-border-width: 5px; --selected-item-border-width: 5px;
--collapsible-toggle-background: var(--light);
--collapsible-min-button-height: 44px;
--collapsible-toggle-text: var(--text-color);
--background-gradient-rgb: #{$ion-item-background-rgb};
--core-login-background: var(--white); --core-login-background: var(--white);
--core-login-text-color: var(--gray-900); --core-login-text-color: var(--gray-900);
--core-login-input-background: var(--white); --core-login-input-background: var(--white);