commit
63bd215afe
|
@ -11,6 +11,10 @@
|
|||
<!-- Activity info. -->
|
||||
<core-course-module-info [module]="module" [courseId]="courseId" [description]="displayDescription && description"
|
||||
[component]="component" [componentId]="componentId" (completionChanged)="onCompletionChange()">
|
||||
<div class="addon-mod_resource-afterlink ion-text-wrap" *ngIf="module.afterlink" description>
|
||||
<core-format-text [text]="module.afterlink" contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId">
|
||||
</core-format-text>
|
||||
</div>
|
||||
</core-course-module-info>
|
||||
|
||||
<ion-card class="core-warning-card" *ngIf="warning">
|
||||
|
|
|
@ -8,4 +8,8 @@
|
|||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.addon-mod_resource-afterlink {
|
||||
font-size: var(--text-size);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,6 +112,8 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
|
|||
throw new CoreError(Translate.instant('core.filenotfound'));
|
||||
}
|
||||
|
||||
this.module.afterlink = await AddonModResourceHelper.getAfterLinkDetails(this.module, this.courseId);
|
||||
|
||||
// Get the resource instance to get the latest name/description and to know if it's embedded.
|
||||
const resource = await AddonModResource.getResourceData(this.courseId, this.module.id);
|
||||
this.description = resource.intro || '';
|
||||
|
|
|
@ -21,12 +21,11 @@ import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/
|
|||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
||||
import { CoreFileHelper } from '@services/file-helper';
|
||||
import { CoreMimetypeUtils } from '@services/utils/mimetype';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreTimeUtils } from '@services/utils/time';
|
||||
import { makeSingleton, Translate } from '@singletons';
|
||||
import { AddonModResourceIndexComponent } from '../../components/index';
|
||||
import { AddonModResource, AddonModResourceCustomData } from '../resource';
|
||||
import { AddonModResource } from '../resource';
|
||||
import { AddonModResourceHelper } from '../resource-helper';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
|
||||
/**
|
||||
* Handler to support resource modules.
|
||||
|
@ -94,19 +93,20 @@ export class AddonModResourceModuleHandlerService extends CoreModuleHandlerBase
|
|||
},
|
||||
};
|
||||
|
||||
this.getResourceData(module, courseId, handlerData).then((extra) => {
|
||||
handlerData.extraBadge = extra;
|
||||
const [hideButton, extraBadge] = await Promise.all([
|
||||
CoreUtils.ignoreErrors(this.hideOpenButton(module)),
|
||||
CoreUtils.ignoreErrors(AddonModResourceHelper.getAfterLinkDetails(module, courseId)),
|
||||
]);
|
||||
|
||||
return;
|
||||
}).catch(() => {
|
||||
// Ignore errors.
|
||||
});
|
||||
|
||||
try {
|
||||
handlerData.icon = this.getIconSrc(module);
|
||||
} catch {
|
||||
// Ignore errors.
|
||||
// Check if the button needs to be shown or not.
|
||||
if (hideButton !== undefined) {
|
||||
handlerData.button.hidden = hideButton;
|
||||
}
|
||||
if (extraBadge !== undefined) {
|
||||
handlerData.extraBadge = extraBadge;
|
||||
}
|
||||
|
||||
handlerData.icon = this.getIconSrc(module);
|
||||
|
||||
return handlerData;
|
||||
}
|
||||
|
@ -127,102 +127,6 @@ export class AddonModResourceModuleHandlerService extends CoreModuleHandlerBase
|
|||
return status !== CoreConstants.DOWNLOADED || AddonModResourceHelper.isDisplayedInIframe(module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the activity icon and data.
|
||||
*
|
||||
* @param module The module object.
|
||||
* @param courseId The course ID.
|
||||
* @returns Resource data.
|
||||
*/
|
||||
protected async getResourceData(
|
||||
module: CoreCourseModuleData,
|
||||
courseId: number,
|
||||
handlerData: CoreCourseModuleHandlerData,
|
||||
): Promise<string> {
|
||||
const promises: Promise<void>[] = [];
|
||||
let options: AddonModResourceCustomData = {};
|
||||
|
||||
// Check if the button needs to be shown or not.
|
||||
promises.push(this.hideOpenButton(module).then((hideOpenButton) => {
|
||||
if (!handlerData.button) {
|
||||
return;
|
||||
}
|
||||
|
||||
handlerData.button.hidden = hideOpenButton;
|
||||
|
||||
return;
|
||||
}));
|
||||
|
||||
if (module.customdata !== undefined) {
|
||||
options = CoreTextUtils.unserialize(CoreTextUtils.parseJSON(module.customdata));
|
||||
} else {
|
||||
// Get the resource data.
|
||||
promises.push(AddonModResource.getResourceData(courseId, module.id).then((info) => {
|
||||
options = CoreTextUtils.unserialize(info.displayoptions);
|
||||
|
||||
return;
|
||||
}));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
if (module.contentsinfo) {
|
||||
// No need to use the list of files.
|
||||
return CoreTextUtils.cleanTags(module.afterlink);
|
||||
}
|
||||
|
||||
if (!module.contents || !module.contents[0]) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const extra: string[] = [];
|
||||
const files = module.contents;
|
||||
const mainFile = files[0];
|
||||
|
||||
if (options.showsize) {
|
||||
const size = options.filedetails
|
||||
? options.filedetails.size
|
||||
: files.reduce((result, file) => result + (file.filesize || 0), 0);
|
||||
|
||||
extra.push(CoreTextUtils.bytesToSize(size, 1));
|
||||
}
|
||||
|
||||
if (options.showtype) {
|
||||
// We should take it from options.filedetails.size if available but it's already translated.
|
||||
extra.push(CoreMimetypeUtils.getMimetypeDescription(mainFile));
|
||||
}
|
||||
|
||||
if (options.showdate) {
|
||||
const timecreated = 'timecreated' in mainFile ? mainFile.timecreated : 0;
|
||||
|
||||
if (options.filedetails && options.filedetails.modifieddate) {
|
||||
extra.push(Translate.instant(
|
||||
'addon.mod_resource.modifieddate',
|
||||
{ $a: CoreTimeUtils.userDate(options.filedetails.modifieddate * 1000, 'core.strftimedatetimeshort') },
|
||||
));
|
||||
} else if (options.filedetails && options.filedetails.uploadeddate) {
|
||||
extra.push(Translate.instant(
|
||||
'addon.mod_resource.uploadeddate',
|
||||
{ $a: CoreTimeUtils.userDate(options.filedetails.uploadeddate * 1000, 'core.strftimedatetimeshort') },
|
||||
));
|
||||
} else if ((mainFile.timemodified || 0) > timecreated + CoreConstants.SECONDS_MINUTE * 5) {
|
||||
/* Modified date may be up to several minutes later than uploaded date just because
|
||||
teacher did not submit the form promptly. Give teacher up to 5 minutes to do it. */
|
||||
extra.push(Translate.instant(
|
||||
'addon.mod_resource.modifieddate',
|
||||
{ $a: CoreTimeUtils.userDate((mainFile.timemodified || 0) * 1000, 'core.strftimedatetimeshort') },
|
||||
));
|
||||
} else {
|
||||
extra.push(Translate.instant(
|
||||
'addon.mod_resource.uploadeddate',
|
||||
{ $a: CoreTimeUtils.userDate(timecreated * 1000, 'core.strftimedatetimeshort') },
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return extra.join(' · ');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
|
|
@ -27,8 +27,10 @@ import { CoreMimetypeUtils } from '@services/utils/mimetype';
|
|||
import { CoreUtilsOpenFileOptions } from '@services/utils/utils';
|
||||
import { makeSingleton, Translate } from '@singletons';
|
||||
import { CorePath } from '@singletons/path';
|
||||
import { AddonModResource, AddonModResourceProvider } from './resource';
|
||||
import { AddonModResource, AddonModResourceCustomData, AddonModResourceProvider } from './resource';
|
||||
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreTimeUtils } from '@services/utils/time';
|
||||
|
||||
/**
|
||||
* Service that provides helper functions for resources.
|
||||
|
@ -222,5 +224,116 @@ export class AddonModResourceHelperProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resource show options.
|
||||
*
|
||||
* @param module The module object.
|
||||
* @param courseId The course ID.
|
||||
* @returns Resource options.
|
||||
*/
|
||||
protected async getModuleOptions(module: CoreCourseModuleData, courseId: number): Promise<AddonModResourceCustomData> {
|
||||
if (module.customdata !== undefined) {
|
||||
const customData: { displayoptions: string } | string = CoreTextUtils.parseJSON(module.customdata);
|
||||
const displayOptions = typeof customData === 'object' ? customData.displayoptions : customData;
|
||||
|
||||
return CoreTextUtils.unserialize(displayOptions);
|
||||
}
|
||||
|
||||
// Get the resource data. Legacy version (from 3.5 to 3.6.6)
|
||||
const info = await AddonModResource.getResourceData(courseId, module.id);
|
||||
const options: AddonModResourceCustomData = CoreTextUtils.unserialize(info.displayoptions);
|
||||
|
||||
if (!module.contents?.[0] || options.filedetails !== undefined) {
|
||||
// Contents attribute should be loaded at this point and it's needed to get mainFile.
|
||||
// Filedetails won't be usually loaded, but if it's there's no need to check mainFile.
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
// Fill filedetails checking files in contents.
|
||||
options.filedetails = {};
|
||||
|
||||
const files = module.contents;
|
||||
const mainFile = files[0];
|
||||
|
||||
if (options.showsize) {
|
||||
options.filedetails.size = files.reduce((result, file) => result + (file.filesize || 0), 0);
|
||||
}
|
||||
|
||||
if (options.showtype) {
|
||||
options.filedetails.type = CoreMimetypeUtils.getMimetypeDescription(mainFile);
|
||||
}
|
||||
|
||||
if (options.showdate) {
|
||||
const timecreated = 'timecreated' in mainFile ? mainFile.timecreated : 0;
|
||||
|
||||
if ((mainFile.timemodified || 0) > timecreated + CoreConstants.SECONDS_MINUTE * 5) {
|
||||
/* Modified date may be up to several minutes later than uploaded date just because
|
||||
teacher did not submit the form promptly. Give teacher up to 5 minutes to do it. */
|
||||
options.filedetails.modifieddate = mainFile.timemodified || 0;
|
||||
} else {
|
||||
options.filedetails.uploadeddate = timecreated;
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get afterlink details to be shown on the activity card.
|
||||
*
|
||||
* @param module The module object.
|
||||
* @param courseId The course ID.
|
||||
* @returns Description string to be shown on the activity card.
|
||||
*/
|
||||
async getAfterLinkDetails(
|
||||
module: CoreCourseModuleData,
|
||||
courseId: number,
|
||||
): Promise<string> {
|
||||
const options = await this.getModuleOptions(module, courseId);
|
||||
|
||||
if (!options.filedetails) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const details = options.filedetails;
|
||||
|
||||
const extra: string[] = [];
|
||||
|
||||
if (options.showsize && details.size) {
|
||||
extra.push(CoreTextUtils.bytesToSize(details.size, 1));
|
||||
}
|
||||
|
||||
if (options.showtype) {
|
||||
// The order of this if conditions should not be changed.
|
||||
if (details.extension) {
|
||||
// From LMS 4.3 onwards only extension is shown.
|
||||
extra.push(details.extension);
|
||||
} else if (details.mimetype) {
|
||||
// Mostly used from 3.7 to 4.2.
|
||||
extra.push(CoreMimetypeUtils.getMimetypeDescription(details.mimetype));
|
||||
} else if (details.type) {
|
||||
// Used on 3.5 and 3.6 where mimetype populated on getModuleOptions using main file.
|
||||
extra.push(details.type); // Already translated.
|
||||
}
|
||||
}
|
||||
|
||||
if (options.showdate) {
|
||||
if (details.modifieddate) {
|
||||
extra.push(Translate.instant(
|
||||
'addon.mod_resource.modifieddate',
|
||||
{ $a: CoreTimeUtils.userDate(details.modifieddate * 1000, 'core.strftimedatetimeshort') },
|
||||
));
|
||||
} else if (details.uploadeddate) {
|
||||
extra.push(Translate.instant(
|
||||
'addon.mod_resource.uploadeddate',
|
||||
{ $a: CoreTimeUtils.userDate(details.uploadeddate * 1000, 'core.strftimedatetimeshort') },
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return extra.join(' · ');
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonModResourceHelper = makeSingleton(AddonModResourceHelperProvider);
|
||||
|
|
|
@ -200,12 +200,20 @@ export type AddonModResourceResource = {
|
|||
};
|
||||
|
||||
export type AddonModResourceCustomData = {
|
||||
showsize?: boolean;
|
||||
filedetails?: {
|
||||
size: number;
|
||||
modifieddate: number;
|
||||
uploadeddate: number;
|
||||
isref?: boolean; // If file is a reference the 'size' or 'date' attribute can not be cached.
|
||||
// If showsize is true.
|
||||
size?: number; // Size in bytes.
|
||||
// If showtype is true.
|
||||
type?: string; // Mimetype description (already translated).
|
||||
mimetype?: string; // @since LMS 3.7
|
||||
extension?: string; // @since LMS 4.3
|
||||
// If showdate is true.
|
||||
modifieddate?: number; // Only if file has been modified.
|
||||
uploadeddate?: number; // Only if file has NOT been modified.
|
||||
|
||||
};
|
||||
showsize?: boolean;
|
||||
showtype?: boolean;
|
||||
showdate?: boolean;
|
||||
printintro?: boolean;
|
||||
|
|
|
@ -840,6 +840,9 @@ body.core-iframe-fullscreen ion-router-outlet {
|
|||
.item-dimmed {
|
||||
opacity: 0.7;
|
||||
--background: var(--light);
|
||||
ion-item {
|
||||
--background: var(--light);
|
||||
}
|
||||
}
|
||||
|
||||
// Extra text colors.
|
||||
|
|
Loading…
Reference in New Issue