UNPKG

scorm-again

Version:

A modern SCORM JavaScript run-time library for AICC, SCORM 1.2, and SCORM 2004

339 lines (293 loc) 8.06 kB
export const SECONDS_PER_SECOND = 1.0; export const SECONDS_PER_MINUTE = 60; export const SECONDS_PER_HOUR = 60 * SECONDS_PER_MINUTE; export const SECONDS_PER_DAY = 24 * SECONDS_PER_HOUR; type Designation = { [key: string]: number; }; type StringKeyMap = { [key: string]: any; }; const designations: Designation = { D: SECONDS_PER_DAY, H: SECONDS_PER_HOUR, M: SECONDS_PER_MINUTE, S: SECONDS_PER_SECOND, }; /** * Converts a Number to a String of HH:MM:SS * * @param {number} totalSeconds * @return {string} */ export function getSecondsAsHHMMSS(totalSeconds: number | null): string { // SCORM spec does not deal with negative durations, give zero back if (!totalSeconds || totalSeconds <= 0) { return "00:00:00"; } const hours = Math.floor(totalSeconds / SECONDS_PER_HOUR); const dateObj = new Date(totalSeconds * 1000); const minutes = dateObj.getUTCMinutes(); // make sure we add any possible decimal value const seconds = dateObj.getSeconds(); const ms = totalSeconds % 1.0; let msStr = ""; if (countDecimals(ms) > 0) { if (countDecimals(ms) > 2) { msStr = ms.toFixed(2); } else { msStr = String(ms); } msStr = "." + msStr.split(".")[1]; } return ( (hours + ":" + minutes + ":" + seconds).replace(/\b\d\b/g, "0$&") + msStr ); } /** * Calculate the number of seconds from ISO 8601 Duration * * @param {number} seconds * @return {string} */ export function getSecondsAsISODuration(seconds: number | null): string { // SCORM spec does not deal with negative durations, give zero back if (!seconds || seconds <= 0) { return "PT0S"; } let duration = "P"; let remainder = seconds; for (const designationsKey in designations) { const current_seconds = designations[designationsKey] || 1; let value = Math.floor(remainder / current_seconds); remainder = remainder % current_seconds; if (countDecimals(remainder) > 2) { remainder = Number(Number(remainder).toFixed(2)); } // If we have anything left in the remainder, and we're currently adding // seconds to the duration, go ahead and add the decimal to the seconds if (designationsKey === "S" && remainder > 0) { value += remainder; } if (value) { if ( (duration.indexOf("D") > 0 || designationsKey === "H" || designationsKey === "M" || designationsKey === "S") && duration.indexOf("T") === -1 ) { duration += "T"; } duration += `${value}${designationsKey}`; } } return duration; } /** * Calculate the number of seconds from HH:MM:SS.DDDDDD * * @param {string} timeString * @param {RegExp} timeRegex * @return {number} */ export function getTimeAsSeconds( timeString: string | number | boolean | null, timeRegex: RegExp | string, ): number { if (typeof timeString === "number" || typeof timeString === "boolean") { timeString = String(timeString); } if (typeof timeRegex === "string") { timeRegex = new RegExp(timeRegex); } if (!timeString || !timeString.match(timeRegex)) { return 0; } const parts = timeString.split(":"); const hours = Number(parts[0]); const minutes = Number(parts[1]); const seconds = Number(parts[2]); return hours * 3600 + minutes * 60 + seconds; } /** * Calculate the number of seconds from ISO 8601 Duration * * @param {string} duration * @param {RegExp} durationRegex * @return {number} */ export function getDurationAsSeconds( duration: string | null, durationRegex: RegExp | string, ): number { if (typeof durationRegex === "string") { durationRegex = new RegExp(durationRegex); } if (!duration || !duration.match(durationRegex)) { return 0; } const [, years, _, , days, hours, minutes, seconds] = new RegExp(durationRegex).exec(duration) || []; let result = 0.0; result += Number(seconds) || 0.0; result += Number(minutes) * 60.0 || 0.0; result += Number(hours) * 3600.0 || 0.0; result += Number(days) * (60 * 60 * 24.0) || 0.0; result += Number(years) * (60 * 60 * 24 * 365.0) || 0.0; return result; } /** * Adds together two ISO8601 Duration strings * * @param {string} first * @param {string} second * @param {RegExp|string} durationRegex * @return {string} */ export function addTwoDurations( first: string, second: string, durationRegex: RegExp | string, ): string { const regex: RegExp = typeof durationRegex === "string" ? new RegExp(durationRegex) : durationRegex; return getSecondsAsISODuration( getDurationAsSeconds(first, regex) + getDurationAsSeconds(second, regex), ); } /** * Add together two HH:MM:SS.DD strings * * @param {string} first * @param {string} second * @param {RegExp} timeRegex * @return {string} */ export function addHHMMSSTimeStrings( first: string, second: string, timeRegex: RegExp | string, ): string { if (typeof timeRegex === "string") { timeRegex = new RegExp(timeRegex); } return getSecondsAsHHMMSS( getTimeAsSeconds(first, timeRegex) + getTimeAsSeconds(second, timeRegex), ); } /** * Flatten a JSON object down to string paths for each values * @param {object} data * @return {object} */ export function flatten(data: StringKeyMap): object { const result: StringKeyMap = {}; /** * Recurse through the object * @param {*} cur * @param {*} prop */ function recurse(cur: any, prop: any) { if (Object(cur) !== cur) { result[prop] = cur; } else if (Array.isArray(cur)) { for (let i = 0, l = cur.length; i < l; i++) { recurse(cur[i], prop + "[" + i + "]"); if (l === 0) result[prop] = []; } } else { let isEmpty = true; for (const p in cur) { if ({}.hasOwnProperty.call(cur, p)) { isEmpty = false; recurse(cur[p], prop ? prop + "." + p : p); } } if (isEmpty && prop) result[prop] = {}; } } recurse(data, ""); return result; } /** * Un-flatten a flat JSON object * @param {object} data * @return {object} */ export function unflatten(data: StringKeyMap): object { "use strict"; if (Object(data) !== data || Array.isArray(data)) return data; const regex = /\.?([^.[\]]+)|\[(\d+)]/g; const result: StringKeyMap = {}; for (const p in data) { if ({}.hasOwnProperty.call(data, p)) { let cur = result; let prop = ""; let m = regex.exec(p); while (m) { cur = cur[prop] || (cur[prop] = m[2] ? [] : {}); prop = m[2] || m[1] || ""; m = regex.exec(p); } cur[prop] = data[p]; } } return result[""] || result; } /** * Counts the number of decimal places * @param {number} num * @return {number} */ export function countDecimals(num: number): number { if (Math.floor(num) === num || String(num).indexOf(".") < 0) return 0; const parts = num.toString().split(".")[1]; return parts?.length || 0; } /** * Formats the SCORM messages for easy reading * * @param {string} functionName * @param {string} message * @param {string} CMIElement * @return {string} */ export function formatMessage( functionName: string, message: string, CMIElement?: string, ): string { const baseLength = 20; let messageString = ""; messageString += functionName; let fillChars = baseLength - messageString.length; for (let i = 0; i < fillChars; i++) { messageString += " "; } messageString += ": "; if (CMIElement) { const CMIElementBaseLength = 70; messageString += CMIElement; fillChars = CMIElementBaseLength - messageString.length; for (let j = 0; j < fillChars; j++) { messageString += " "; } } if (message) { messageString += message; } return messageString; } /** * Checks to see if {str} contains {tester} * * @param {string} str String to check against * @param {string} tester String to check for * @return {boolean} */ export function stringMatches(str: string, tester: string): boolean { return str?.match(tester) !== null; }