commit
56a3287aff
|
@ -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);
|
||||||
|
}
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
|
@ -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);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 }}
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
:host {
|
|
||||||
.core-module-automatic-completion-conditions {
|
|
||||||
ion-badge {
|
|
||||||
font-weight: normal;
|
|
||||||
margin-right: 5px;
|
|
||||||
|
|
||||||
&[color="medium"] {
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
|
@ -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 }}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)">
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue