|
@ -11,10 +11,10 @@
|
||||||
<!-- Activity info. -->
|
<!-- Activity info. -->
|
||||||
<core-course-module-info [module]="module" [description]="description" [component]="component" [componentId]="componentId"
|
<core-course-module-info [module]="module" [description]="description" [component]="component" [componentId]="componentId"
|
||||||
[courseId]="courseId" [hasDataToSync]="hasOffline" (completionChanged)="onCompletionChange()">
|
[courseId]="courseId" [hasDataToSync]="hasOffline" (completionChanged)="onCompletionChange()">
|
||||||
<ion-list inset="true" description *ngIf="assign && assign.introattachments?.length && !assign.submissionattachments">
|
<div description *ngIf="assign && assign.introattachments?.length && !assign.submissionattachments">
|
||||||
<core-file *ngFor="let file of assign.introattachments" [file]="file" [component]="component" [componentId]="componentId">
|
<core-file *ngFor="let file of assign.introattachments" [file]="file" [component]="component" [componentId]="componentId">
|
||||||
</core-file>
|
</core-file>
|
||||||
</ion-list>
|
</div>
|
||||||
</core-course-module-info>
|
</core-course-module-info>
|
||||||
|
|
||||||
<!-- User can view all submissions (teacher). -->
|
<!-- User can view all submissions (teacher). -->
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
<ion-list *ngIf="contents && (contents.files.length + contents.folders.length > 0)">
|
<ion-list *ngIf="contents && (contents.files.length + contents.folders.length > 0)">
|
||||||
<ng-container *ngFor="let folder of contents.folders">
|
<ng-container *ngFor="let folder of contents.folders">
|
||||||
<ion-item class="item-file" (click)="openFolder(folder)" detail="true" button>
|
<ion-item class="ion-text-wrap item-file item-directory" (click)="openFolder(folder)" detail="true" button>
|
||||||
<ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.folder' | translate"></ion-icon>
|
<ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.folder' | translate"></ion-icon>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p class="item-heading">{{folder.filename}}</p>
|
<p class="item-heading">{{folder.filename}}</p>
|
||||||
|
|
|
@ -54,6 +54,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
|
||||||
if (this.subfolder) {
|
if (this.subfolder) {
|
||||||
this.description = this.folderInstance ? this.folderInstance.intro : this.module.description;
|
this.description = this.folderInstance ? this.folderInstance.intro : this.module.description;
|
||||||
this.contents = this.subfolder;
|
this.contents = this.subfolder;
|
||||||
|
this.sortFilesAndFolders();
|
||||||
|
|
||||||
this.showLoading = false;
|
this.showLoading = false;
|
||||||
|
|
||||||
|
@ -88,6 +89,30 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
|
||||||
|
|
||||||
this.description = this.folderInstance ? this.folderInstance.intro : this.module.description;
|
this.description = this.folderInstance ? this.folderInstance.intro : this.module.description;
|
||||||
this.contents = AddonModFolderHelper.formatContents(contents);
|
this.contents = AddonModFolderHelper.formatContents(contents);
|
||||||
|
this.sortFilesAndFolders();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort files and folders alphabetically.
|
||||||
|
*/
|
||||||
|
protected sortFilesAndFolders(): void {
|
||||||
|
if (!this.contents) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.contents.folders.sort((a, b) => {
|
||||||
|
const compareA = a.filename.toLowerCase();
|
||||||
|
const compareB = b.filename.toLowerCase();
|
||||||
|
|
||||||
|
return compareA.localeCompare(compareB);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.contents.files.sort((a, b) => {
|
||||||
|
const compareA = a.filename.toLowerCase();
|
||||||
|
const compareB = b.filename.toLowerCase();
|
||||||
|
|
||||||
|
return compareA.localeCompare(compareB);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -40,6 +40,8 @@ ion-item {
|
||||||
div.core-notification-icon,
|
div.core-notification-icon,
|
||||||
core-mod-icon.core-notification-icon {
|
core-mod-icon.core-notification-icon {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
max-width: var(--core-avatar-size);
|
||||||
|
max-height: var(--core-avatar-size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,10 +37,8 @@
|
||||||
<!-- List of files. -->
|
<!-- List of files. -->
|
||||||
<ion-list *ngIf="files && files.length > 0">
|
<ion-list *ngIf="files && files.length > 0">
|
||||||
<ng-container *ngFor="let file of files">
|
<ng-container *ngFor="let file of files">
|
||||||
<ion-item button *ngIf="file.isdir" class="item-file" (click)="openFolder(file)" detail="true">
|
<ion-item button *ngIf="file.isdir" class="ion-text-wrap item-file item-directory" (click)="openFolder(file)" detail="true">
|
||||||
<ion-thumbnail slot="start">
|
<ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.folder' | translate"></ion-icon>
|
||||||
<img [src]="file.imgPath" alt="" role="presentation">
|
|
||||||
</ion-thumbnail>
|
|
||||||
<ion-label>{{file.filename}}</ion-label>
|
<ion-label>{{file.filename}}</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<core-file *ngIf="!file.isdir" [file]="file" [component]="component" [componentId]="file.contextid"></core-file>
|
<core-file *ngIf="!file.isdir" [file]="file" [component]="component" [componentId]="file.contextid"></core-file>
|
||||||
|
|
Before Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 539 B |
After Width: | Height: | Size: 543 B |
Before Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 888 B |
Before Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 888 B |
Before Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 5.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 1.1 KiB |
|
@ -1,11 +1,15 @@
|
||||||
|
<ion-card class="card-file">
|
||||||
<ion-item *ngIf="file" button class="ion-text-wrap item-file" (click)="download($event, true)" detail="false">
|
<ion-item *ngIf="file" button class="ion-text-wrap item-file" (click)="download($event, true)" detail="false">
|
||||||
<ion-thumbnail slot="start">
|
<ion-thumbnail slot="start">
|
||||||
<img [src]="fileIcon" alt="" role="presentation" />
|
<img [src]="fileIcon" alt="" role="presentation" />
|
||||||
</ion-thumbnail>
|
</ion-thumbnail>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p class="item-heading">{{fileName}}</p>
|
<p class="item-heading">{{fileName}}</p>
|
||||||
<p *ngIf="fileSizeReadable">{{ fileSizeReadable }}</p>
|
<p *ngIf="fileSizeReadable || showTime">
|
||||||
<p *ngIf="showTime">{{ timemodified * 1000 | coreFormatDate }}</p>
|
<ng-container *ngIf="fileSizeReadable">{{ fileSizeReadable }}</ng-container>
|
||||||
|
<ng-container *ngIf="fileSizeReadable && showTime"> · </ng-container>
|
||||||
|
<ng-container *ngIf="showTime">{{ timemodified * 1000 | coreFormatDate }}</ng-container>
|
||||||
|
</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<div slot="end" class="flex-row">
|
<div slot="end" class="flex-row">
|
||||||
<core-download-refresh [status]="state" [enabled]="canDownload" [loading]="isDownloading" [canTrustDownload]="!alwaysDownload"
|
<core-download-refresh [status]="state" [enabled]="canDownload" [loading]="isDownloading" [canTrustDownload]="!alwaysDownload"
|
||||||
|
@ -16,9 +20,10 @@
|
||||||
<ion-icon slot="icon-only" [name]="openButtonIcon" aria-hidden="true"></ion-icon>
|
<ion-icon slot="icon-only" [name]="openButtonIcon" aria-hidden="true"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
<ion-button fill="clear" *ngIf="!isDownloading && canDelete" (click)="delete($event)" [attr.aria-label]="'core.delete' | translate"
|
<ion-button fill="clear" *ngIf="!isDownloading && canDelete" (click)="delete($event)"
|
||||||
color="danger">
|
[attr.aria-label]="'core.delete' | translate" color="danger">
|
||||||
<ion-icon slot="icon-only" name="fas-trash" aria-hidden="true"></ion-icon>
|
<ion-icon slot="icon-only" name="fas-trash" aria-hidden="true"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</div>
|
</div>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
</ion-card>
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
<form (ngSubmit)="changeName(newFileName, $event)" #nameForm>
|
<form (ngSubmit)="changeName(newFileName, $event)" #nameForm>
|
||||||
|
<ion-card class="card-file">
|
||||||
<ion-item class="ion-text-wrap item-file" (click)="openFile($event)" button detail="false">
|
<ion-item class="ion-text-wrap item-file" (click)="openFile($event)" button detail="false">
|
||||||
<ion-thumbnail slot="start">
|
<ion-thumbnail slot="start">
|
||||||
<img [src]="fileIcon" [alt]="fileExtension" role="presentation" />
|
<img [src]="fileIcon" [alt]="fileExtension" role="presentation" />
|
||||||
</ion-thumbnail>
|
</ion-thumbnail>
|
||||||
|
|
||||||
<ion-label>
|
<ion-label *ngIf="!editMode">
|
||||||
<!-- File name and edit button (if editable). -->
|
<!-- File name and edit button (if editable). -->
|
||||||
<p class="item-heading" *ngIf="!editMode">{{fileName}}</p>
|
<p class="item-heading">{{fileName}}</p>
|
||||||
<!-- More data about the file. -->
|
<!-- More data about the file. -->
|
||||||
<p *ngIf="size && !editMode">{{ size }}</p>
|
<p *ngIf="size || timemodified">
|
||||||
<p *ngIf="timemodified && !editMode">{{ timemodified }}</p>
|
<ng-container *ngIf="size">{{ size }}</ng-container>
|
||||||
|
<ng-container *ngIf="size && timemodified"> · </ng-container>
|
||||||
|
<ng-container *ngIf="timemodified">{{ timemodified }}</ng-container>
|
||||||
|
</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
|
|
||||||
<!-- Form to edit the file's name. -->
|
<!-- Form to edit the file's name. -->
|
||||||
|
@ -23,19 +27,22 @@
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
<ng-container *ngIf="manage">
|
<ng-container *ngIf="manage">
|
||||||
|
<ion-button *ngIf="editMode" fill="clear" [attr.aria-label]="'core.save' | translate" color="success" type="submit"
|
||||||
|
(click)="changeName(newFileName, $event)">
|
||||||
|
<ion-icon name="fas-check" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
|
||||||
<ion-button *ngIf="!editMode" fill="clear" [core-suppress-events] (onClick)="activateEdit($event)"
|
<ion-button *ngIf="!editMode" fill="clear" [core-suppress-events] (onClick)="activateEdit($event)"
|
||||||
[attr.aria-label]="'core.edit' | translate">
|
[attr.aria-label]="'core.edit' | translate">
|
||||||
<ion-icon name="fas-pen" slot="icon-only" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-pen" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
<ion-button *ngIf="editMode" fill="clear" [attr.aria-label]="'core.save' | translate" color="success" type="submit">
|
<ion-button *ngIf="!editMode" fill="clear" (click)="deleteFile($event)" [attr.aria-label]="'core.delete' | translate"
|
||||||
<ion-icon name="fas-check" slot="icon-only" aria-hidden="true"></ion-icon>
|
color="danger">
|
||||||
</ion-button>
|
|
||||||
|
|
||||||
<ion-button fill="clear" (click)="deleteFile($event)" [attr.aria-label]="'core.delete' | translate" color="danger">
|
|
||||||
<ion-icon name="fas-trash" slot="icon-only" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-trash" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
</ion-card>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -56,7 +56,7 @@ export class CoreLocalFileComponent implements OnInit {
|
||||||
timemodified?: string;
|
timemodified?: string;
|
||||||
newFileName = '';
|
newFileName = '';
|
||||||
editMode = false;
|
editMode = false;
|
||||||
relativePath?: string;
|
relativePath = '';
|
||||||
isIOS = false;
|
isIOS = false;
|
||||||
openButtonIcon = '';
|
openButtonIcon = '';
|
||||||
openButtonLabel = '';
|
openButtonLabel = '';
|
||||||
|
@ -112,7 +112,7 @@ export class CoreLocalFileComponent implements OnInit {
|
||||||
* @param isOpenButton Whether the open button was clicked.
|
* @param isOpenButton Whether the open button was clicked.
|
||||||
*/
|
*/
|
||||||
async openFile(e: Event, isOpenButton = false): Promise<void> {
|
async openFile(e: Event, isOpenButton = false): Promise<void> {
|
||||||
if (this.editMode) {
|
if (this.editMode || !this.file) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ export class CoreLocalFileComponent implements OnInit {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CoreFileHelper.isOpenableInApp(this.file!)) {
|
if (!CoreFileHelper.isOpenableInApp(this.file)) {
|
||||||
try {
|
try {
|
||||||
await CoreFileHelper.showConfirmOpenUnsupportedFile();
|
await CoreFileHelper.showConfirmOpenUnsupportedFile();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -139,7 +139,7 @@ export class CoreLocalFileComponent implements OnInit {
|
||||||
options.iOSOpenFileAction = this.defaultIsOpenWithPicker ? OpenFileAction.OPEN : OpenFileAction.OPEN_WITH;
|
options.iOSOpenFileAction = this.defaultIsOpenWithPicker ? OpenFileAction.OPEN : OpenFileAction.OPEN_WITH;
|
||||||
}
|
}
|
||||||
|
|
||||||
CoreUtils.openFile(this.file!.toURL(), options);
|
CoreUtils.openFile(this.file.toURL(), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -148,11 +148,15 @@ export class CoreLocalFileComponent implements OnInit {
|
||||||
* @param e Click event.
|
* @param e Click event.
|
||||||
*/
|
*/
|
||||||
activateEdit(e: Event): void {
|
activateEdit(e: Event): void {
|
||||||
|
if (!this.file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
this.editMode = true;
|
this.editMode = true;
|
||||||
this.newFileName = this.file!.name;
|
this.newFileName = this.file.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -162,10 +166,14 @@ export class CoreLocalFileComponent implements OnInit {
|
||||||
* @param e Click event.
|
* @param e Click event.
|
||||||
*/
|
*/
|
||||||
async changeName(newName: string, e: Event): Promise<void> {
|
async changeName(newName: string, e: Event): Promise<void> {
|
||||||
|
if (!this.file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
if (newName == this.file!.name) {
|
if (newName == this.file.name) {
|
||||||
// Name hasn't changed, stop.
|
// Name hasn't changed, stop.
|
||||||
this.editMode = false;
|
this.editMode = false;
|
||||||
CoreForms.triggerFormCancelledEvent(this.formElement, CoreSites.getCurrentSiteId());
|
CoreForms.triggerFormCancelledEvent(this.formElement, CoreSites.getCurrentSiteId());
|
||||||
|
@ -174,7 +182,7 @@ export class CoreLocalFileComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
const modal = await CoreDomUtils.showModalLoading();
|
const modal = await CoreDomUtils.showModalLoading();
|
||||||
const fileAndDir = CoreFile.getFileAndDirectoryFromPath(this.relativePath!);
|
const fileAndDir = CoreFile.getFileAndDirectoryFromPath(this.relativePath);
|
||||||
const newPath = CoreText.concatenatePaths(fileAndDir.directory, newName);
|
const newPath = CoreText.concatenatePaths(fileAndDir.directory, newName);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -186,7 +194,7 @@ export class CoreLocalFileComponent implements OnInit {
|
||||||
} catch {
|
} catch {
|
||||||
try {
|
try {
|
||||||
// File doesn't exist, move it.
|
// File doesn't exist, move it.
|
||||||
const fileEntry = await CoreFile.moveFile(this.relativePath!, newPath);
|
const fileEntry = await CoreFile.moveFile(this.relativePath, newPath);
|
||||||
|
|
||||||
CoreForms.triggerFormSubmittedEvent(this.formElement, false, CoreSites.getCurrentSiteId());
|
CoreForms.triggerFormSubmittedEvent(this.formElement, false, CoreSites.getCurrentSiteId());
|
||||||
|
|
||||||
|
@ -219,7 +227,7 @@ export class CoreLocalFileComponent implements OnInit {
|
||||||
|
|
||||||
modal = await CoreDomUtils.showModalLoading('core.deleting', true);
|
modal = await CoreDomUtils.showModalLoading('core.deleting', true);
|
||||||
|
|
||||||
await CoreFile.removeFile(this.relativePath!);
|
await CoreFile.removeFile(this.relativePath);
|
||||||
|
|
||||||
this.onDelete.emit();
|
this.onDelete.emit();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -208,7 +208,11 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
|
||||||
this.expandedHeader = this.page?.querySelector('ion-item[collapsible]') ?? undefined;
|
this.expandedHeader = this.page?.querySelector('ion-item[collapsible]') ?? undefined;
|
||||||
|
|
||||||
if (!this.expandedHeader) {
|
if (!this.expandedHeader) {
|
||||||
|
this.enabled = false;
|
||||||
|
this.setEnabled(this.enabled);
|
||||||
|
|
||||||
throw new Error('[collapsible-header] Couldn\'t initialize expanded header');
|
throw new Error('[collapsible-header] Couldn\'t initialize expanded header');
|
||||||
|
|
||||||
}
|
}
|
||||||
this.expandedHeader.classList.add('collapsible-header-expanded');
|
this.expandedHeader.classList.add('collapsible-header-expanded');
|
||||||
|
|
||||||
|
@ -384,11 +388,11 @@ export class CoreCollapsibleHeaderDirective implements OnInit, OnChanges, OnDest
|
||||||
* @param enable True to enable, false otherwise
|
* @param enable True to enable, false otherwise
|
||||||
*/
|
*/
|
||||||
async setEnabled(enable: boolean): Promise<void> {
|
async setEnabled(enable: boolean): Promise<void> {
|
||||||
if (!this.page || !this.content) {
|
if (!this.page) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enable) {
|
if (enable && this.content) {
|
||||||
const contentScroll = await this.content.getScrollElement();
|
const contentScroll = await this.content.getScrollElement();
|
||||||
|
|
||||||
// Do nothing, since scroll has already started on the page.
|
// Do nothing, since scroll has already started on the page.
|
||||||
|
|
|
@ -15,11 +15,14 @@
|
||||||
import { Directive, ElementRef, Input, OnDestroy, OnInit } from '@angular/core';
|
import { Directive, ElementRef, Input, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||||
import { CoreLoadingComponent } from '@components/loading/loading';
|
import { CoreLoadingComponent } from '@components/loading/loading';
|
||||||
|
import { CoreSettingsHelper } from '@features/settings/services/settings-helper';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
|
import { CoreColors } from '@singletons/colors';
|
||||||
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
||||||
import { CoreDom } from '@singletons/dom';
|
import { CoreDom } from '@singletons/dom';
|
||||||
import { CoreEventObserver } from '@singletons/events';
|
import { CoreEventObserver } from '@singletons/events';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
import { CoreFormatTextDirective } from './format-text';
|
import { CoreFormatTextDirective } from './format-text';
|
||||||
|
|
||||||
const defaultMaxHeight = 80;
|
const defaultMaxHeight = 80;
|
||||||
|
@ -50,6 +53,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
|
||||||
protected maxHeight = defaultMaxHeight;
|
protected maxHeight = defaultMaxHeight;
|
||||||
protected expandedHeight = 0;
|
protected expandedHeight = 0;
|
||||||
protected resizeListener?: CoreEventObserver;
|
protected resizeListener?: CoreEventObserver;
|
||||||
|
protected darkModeListener?: Subscription;
|
||||||
protected domPromise?: CoreCancellablePromise<void>;
|
protected domPromise?: CoreCancellablePromise<void>;
|
||||||
protected uniqueId: string;
|
protected uniqueId: string;
|
||||||
|
|
||||||
|
@ -92,6 +96,10 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
|
||||||
this.resizeListener = CoreDom.onWindowResize(() => {
|
this.resizeListener = CoreDom.onWindowResize(() => {
|
||||||
this.calculateHeight();
|
this.calculateHeight();
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
|
this.darkModeListener = CoreSettingsHelper.onDarkModeChange().subscribe(() => {
|
||||||
|
this.setGradientColor();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -135,7 +143,34 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
|
||||||
this.element.classList.remove('collapsible-loading-height');
|
this.element.classList.remove('collapsible-loading-height');
|
||||||
|
|
||||||
// If cannot calculate height, shorten always.
|
// If cannot calculate height, shorten always.
|
||||||
this.setExpandButtonEnabled(!this.expandedHeight || this.expandedHeight >= this.maxHeight);
|
const enable = !this.expandedHeight || this.expandedHeight >= this.maxHeight;
|
||||||
|
this.setExpandButtonEnabled(enable);
|
||||||
|
this.setGradientColor();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the gradient color based on the background.
|
||||||
|
*/
|
||||||
|
protected setGradientColor(): void {
|
||||||
|
if (!this.toggleExpandEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let coloredElement: HTMLElement | null = this.element;
|
||||||
|
let backgroundColor = [0, 0, 0, 0];
|
||||||
|
let background = '';
|
||||||
|
while (coloredElement && backgroundColor[3] === 0) {
|
||||||
|
background = getComputedStyle(coloredElement).backgroundColor;
|
||||||
|
backgroundColor = CoreColors.getColorRGBA(background);
|
||||||
|
coloredElement = coloredElement.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backgroundColor[3] !== 0) {
|
||||||
|
delete(backgroundColor[3]);
|
||||||
|
const bgList = backgroundColor.join(',');
|
||||||
|
this.element.style.setProperty('--background-gradient-rgb', `${bgList}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -241,6 +276,7 @@ export class CoreCollapsibleItemDirective implements OnInit, OnDestroy {
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.resizeListener?.off();
|
this.resizeListener?.off();
|
||||||
|
this.darkModeListener?.unsubscribe();
|
||||||
this.domPromise?.cancel();
|
this.domPromise?.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
</core-dynamic-component>
|
</core-dynamic-component>
|
||||||
|
|
||||||
|
|
||||||
<core-block-side-blocks-button slot="fixed" *ngIf="loaded && course && displayBlocks && hasBlocks" contextlevel="course"
|
<core-block-side-blocks-button slot="fixed" *ngIf="loaded && course && displayBlocks && hasBlocks" contextLevel="course"
|
||||||
[instanceId]="course.id">
|
[instanceId]="course.id">
|
||||||
</core-block-side-blocks-button>
|
</core-block-side-blocks-button>
|
||||||
|
|
||||||
|
|
|
@ -14,43 +14,39 @@
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
|
<div class="core-module-info-box">
|
||||||
<!-- Module completion. -->
|
<!-- Module completion. -->
|
||||||
<ion-item class="ion-text-wrap"
|
<div class="core-module-info-completion core-module-info-box-section" *ngIf="showCompletion &&
|
||||||
*ngIf="showCompletion && module.completiondata && (module.completiondata.isautomatic || (showManualCompletion && module.uservisible))">
|
module.completiondata && (module.completiondata.isautomatic || (showManualCompletion && module.uservisible))">
|
||||||
<ion-label>
|
|
||||||
<core-course-module-completion [completion]="module.completiondata" [moduleName]="module.name" [moduleId]="module.id"
|
<core-course-module-completion [completion]="module.completiondata" [moduleName]="module.name" [moduleId]="module.id"
|
||||||
[showCompletionConditions]="true" [showManualCompletion]="showManualCompletion && module.uservisible"
|
[showCompletionConditions]="true" [showManualCompletion]="showManualCompletion && module.uservisible"
|
||||||
(completionChanged)="completionChanged.emit($event)">
|
(completionChanged)="completionChanged.emit($event)">
|
||||||
</core-course-module-completion>
|
</core-course-module-completion>
|
||||||
</ion-label>
|
</div>
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<div class="core-module-dates-availabilityinfo"
|
|
||||||
*ngIf="(module.dates && module.dates.length) || (showAvailabilityInfo && module.availabilityinfo)">
|
|
||||||
<!-- Activity dates. -->
|
<!-- Activity dates. -->
|
||||||
<div *ngIf="module.dates && module.dates.length" class="core-module-dates">
|
<div *ngIf="module.dates && module.dates.length" class="core-module-dates core-module-info-box-section">
|
||||||
<p *ngFor="let date of module.dates">
|
<p *ngFor="let date of module.dates">
|
||||||
<ion-icon name="fas-calendar" aria-hidden="true"></ion-icon><strong>{{ date.label }}</strong> {{ date.timestamp
|
<ion-icon name="fas-calendar" aria-hidden="true"></ion-icon><strong>{{ date.label }}</strong> {{ date.timestamp
|
||||||
*
|
*
|
||||||
1000 | coreFormatDate:'strftimedatetime' }}
|
1000 | coreFormatDate:'strftimedatetime' }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Availability info space. -->
|
<!-- Availability info space. -->
|
||||||
<div class="core-module-availabilityinfo" *ngIf="showAvailabilityInfo">
|
<div class="core-module-availabilityinfo core-module-info-box-section" *ngIf="showAvailabilityInfo">
|
||||||
<ion-icon name="fas-lock" [attr.aria-label]="'core.restricted' | translate"></ion-icon>
|
<ion-icon name="fas-lock" [attr.aria-label]="'core.restricted' | translate"></ion-icon>
|
||||||
<core-format-text [text]="module.availabilityinfo" contextLevel="module" [contextInstanceId]="module.id" [courseId]="module.course">
|
<core-format-text [text]="module.availabilityinfo" contextLevel="module" [contextInstanceId]="module.id" [courseId]="module.course">
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<ion-item class="ion-text-wrap" *ngIf="description">
|
<div class="core-module-info-description core-module-info-box-section" *ngIf="description">
|
||||||
<ion-label>
|
|
||||||
<core-format-text [text]="description" [component]="component" [componentId]="componentId" contextLevel="module"
|
<core-format-text [text]="description" [component]="component" [componentId]="componentId" contextLevel="module"
|
||||||
[contextInstanceId]="module.id" [courseId]="courseId" [collapsible-item]="expandDescription ? null : ''">
|
[contextInstanceId]="module.id" [courseId]="courseId" [collapsible-item]="expandDescription ? null : ''">
|
||||||
</core-format-text>
|
</core-format-text>
|
||||||
</ion-label>
|
</div>
|
||||||
</ion-item>
|
|
||||||
<ng-content select="[description]"></ng-content>
|
<ng-content select="[description]"></ng-content>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
|
|
||||||
|
|
|
@ -23,11 +23,34 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.core-module-dates-availabilityinfo {
|
.core-module-info-box {
|
||||||
background: var(--light);
|
background: var(--light);
|
||||||
border-radius: var(--small-radius);
|
border-radius: var(--small-radius);
|
||||||
padding: 8px;
|
|
||||||
margin: 8px;
|
margin: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
::ng-deep ion-item {
|
||||||
|
--ion-item-background: var(--light);
|
||||||
|
--background: var(--light);
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep ion-card.card-file {
|
||||||
|
--ion-card-horizontal-margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.core-module-info-box-section + .core-module-info-box-section {
|
||||||
|
border-top: 1px solid var(--stroke);
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.core-module-dates ion-icon {
|
||||||
|
margin-left: 4px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.core-module-dates,
|
||||||
|
.core-module-availabilityinfo {
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
ion-icon {
|
ion-icon {
|
||||||
position: static;
|
position: static;
|
||||||
|
@ -40,10 +63,6 @@
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.core-module-dates + .core-module-availabilityinfo {
|
|
||||||
border-top: 1px solid var(--stroke);
|
|
||||||
padding-top: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
core-course-module-completion ::ng-deep ion-button {
|
core-course-module-completion ::ng-deep ion-button {
|
||||||
|
|
|
@ -21,6 +21,10 @@
|
||||||
margin-top: var(--button-vertical-margin);
|
margin-top: var(--button-vertical-margin);
|
||||||
margin-bottom: var(--button-vertical-margin);
|
margin-bottom: var(--button-vertical-margin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:host-context(core-course-format.core-course-format-singleactivity) {
|
:host-context(core-course-format.core-course-format-singleactivity) {
|
||||||
|
|