Compare commits
4 Commits
master
...
timelist-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad30a8fb2c | ||
|
|
cc8f726bd5 | ||
|
|
dafe89a5fa | ||
|
|
d2df694c40 |
@@ -11,8 +11,9 @@ export const MISSION_TIME = 1732413600000; // Saturday, November 23, 2024 6:00:0
|
||||
|
||||
/**
|
||||
* URL Constants
|
||||
* - This is the URL that the browser will be directed to when running visual tests. This URL
|
||||
* - This is the URL that the browser will be directed to when running visual tests. This URL
|
||||
* - hides the tree and inspector to prevent visual noise
|
||||
* - sets the time bounds to a fixed range
|
||||
*/
|
||||
export const VISUAL_URL = './#/browse/mine?tc.mode=fixed&tc.startBound=1693592063607&tc.endBound=1693593893607&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true';
|
||||
export const VISUAL_FIXED_URL =
|
||||
'./#/browse/mine?tc.mode=fixed&tc.startBound=1693592063607&tc.endBound=1693593893607&tc.timeSystem=utc&view=grid&hideInspector=true&hideTree=true';
|
||||
|
||||
@@ -89,17 +89,35 @@ function activitiesWithinTimeBounds(start1, end1, start2, end2) {
|
||||
* @param {string} planObjectUrl
|
||||
*/
|
||||
export async function setBoundsToSpanAllActivities(page, planJson, planObjectUrl) {
|
||||
const activities = Object.values(planJson).flat();
|
||||
// Get the earliest start value
|
||||
const start = Math.min(...activities.map((activity) => activity.start));
|
||||
const start = getEarliestStartTime(planJson);
|
||||
// Get the latest end value
|
||||
const end = Math.max(...activities.map((activity) => activity.end));
|
||||
const end = getLatestEndTime(planJson);
|
||||
// Set the start and end bounds to the earliest start and latest end
|
||||
await page.goto(
|
||||
`${planObjectUrl}?tc.mode=fixed&tc.startBound=${start}&tc.endBound=${end}&tc.timeSystem=utc&view=plan.view`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} planJson
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getEarliestStartTime(planJson) {
|
||||
const activities = Object.values(planJson).flat();
|
||||
return Math.min(...activities.map((activity) => activity.start));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} planJson
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getLatestEndTime(planJson) {
|
||||
const activities = Object.values(planJson).flat();
|
||||
return Math.max(...activities.map((activity) => activity.end));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the Open MCT API to set the status of a plan to 'draft'.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
|
||||
38
e2e/test-data/examplePlans/ExamplePlan_Small3.json
Normal file
38
e2e/test-data/examplePlans/ExamplePlan_Small3.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"Group 1": [
|
||||
{
|
||||
"name": "Time until birthday",
|
||||
"start": 1650320402000,
|
||||
"end": 1660343797000,
|
||||
"type": "Group 1",
|
||||
"color": "orange",
|
||||
"textColor": "white"
|
||||
},
|
||||
{
|
||||
"name": "Time until supper",
|
||||
"start": 1650320402000,
|
||||
"end": 1650420410000,
|
||||
"type": "Group 2",
|
||||
"color": "blue",
|
||||
"textColor": "white"
|
||||
}
|
||||
],
|
||||
"Group 2": [
|
||||
{
|
||||
"name": "Time since the last time I ate",
|
||||
"start": 1650320102001,
|
||||
"end": 1650320102001,
|
||||
"type": "Group 2",
|
||||
"color": "green",
|
||||
"textColor": "white"
|
||||
},
|
||||
{
|
||||
"name": "Time since last accident",
|
||||
"start": 1650320102002,
|
||||
"end": 1650320102002,
|
||||
"type": "Group 1",
|
||||
"color": "yellow",
|
||||
"textColor": "white"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -22,6 +22,15 @@
|
||||
|
||||
const { test, expect } = require('../../../pluginFixtures');
|
||||
const { createDomainObjectWithDefaults, createPlanFromJSON } = require('../../../appActions');
|
||||
const { getEarliestStartTime } = require('../../../helper/planningUtils');
|
||||
const examplePlanSmall3 = require('../../../test-data/examplePlans/ExamplePlan_Small3.json');
|
||||
|
||||
const START_TIME_COLUMN = 0;
|
||||
const END_TIME_COLUMN = 1;
|
||||
const TIME_TO_FROM_COLUMN = 2;
|
||||
const ACTIVITY_COLUMN = 3;
|
||||
const HEADER_ROW = 0;
|
||||
const NUM_COLUMNS = 4;
|
||||
|
||||
const testPlan = {
|
||||
TEST_GROUP: [
|
||||
@@ -84,22 +93,17 @@ test.describe('Time List', () => {
|
||||
});
|
||||
|
||||
await test.step('Create a Plan and add it to the timelist', async () => {
|
||||
const createdPlan = await createPlanFromJSON(page, {
|
||||
await createPlanFromJSON(page, {
|
||||
name: 'Test Plan',
|
||||
json: testPlan
|
||||
json: testPlan,
|
||||
parent: timelist.uuid
|
||||
});
|
||||
|
||||
await page.goto(timelist.url);
|
||||
// Expand the tree to show the plan
|
||||
await page.click("button[title='Show selected item in tree']");
|
||||
await page.dragAndDrop(`role=treeitem[name=/${createdPlan.name}/]`, '.c-object-view');
|
||||
await page.click("button[title='Save']");
|
||||
await page.click("li[title='Save and Finish Editing']");
|
||||
|
||||
const startBound = testPlan.TEST_GROUP[0].start;
|
||||
const endBound = testPlan.TEST_GROUP[testPlan.TEST_GROUP.length - 1].end;
|
||||
|
||||
await page.goto(timelist.url);
|
||||
|
||||
// Switch to fixed time mode with all plan events within the bounds
|
||||
await page.goto(
|
||||
`${timelist.url}?tc.mode=fixed&tc.startBound=${startBound}&tc.endBound=${endBound}&tc.timeSystem=utc&view=timelist.view`
|
||||
@@ -122,3 +126,162 @@ test.describe('Time List', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* The regular expression used to parse the countdown string.
|
||||
* Some examples of valid Countdown strings:
|
||||
* ```
|
||||
* '35D 02:03:04'
|
||||
* '-1D 01:02:03'
|
||||
* '01:02:03'
|
||||
* '-05:06:07'
|
||||
* ```
|
||||
*/
|
||||
const COUNTDOWN_REGEXP = /(-)?(\d+D\s)?(\d{2}):(\d{2}):(\d{2})/;
|
||||
|
||||
/**
|
||||
* @typedef {Object} CountdownObject
|
||||
* @property {string} sign - The sign of the countdown ('-' if the countdown is negative, otherwise undefined).
|
||||
* @property {string} days - The number of days in the countdown (undefined if there are no days).
|
||||
* @property {string} hours - The number of hours in the countdown.
|
||||
* @property {string} minutes - The number of minutes in the countdown.
|
||||
* @property {string} seconds - The number of seconds in the countdown.
|
||||
* @property {string} toString - The countdown string.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Object representing the indices of the capture groups in a countdown regex match.
|
||||
*
|
||||
* @typedef {{ SIGN: number, DAYS: number, HOURS: number, MINUTES: number, SECONDS: number, REGEXP: RegExp }}
|
||||
* @property {number} SIGN - The index for the sign capture group (1 if a '-' sign is present, otherwise undefined).
|
||||
* @property {number} DAYS - The index for the days capture group (2 for the number of days, otherwise undefined).
|
||||
* @property {number} HOURS - The index for the hours capture group (3 for the hour part of the time).
|
||||
* @property {number} MINUTES - The index for the minutes capture group (4 for the minute part of the time).
|
||||
* @property {number} SECONDS - The index for the seconds capture group (5 for the second part of the time).
|
||||
*/
|
||||
const COUNTDOWN = Object.freeze({
|
||||
SIGN: 1,
|
||||
DAYS: 2,
|
||||
HOURS: 3,
|
||||
MINUTES: 4,
|
||||
SECONDS: 5
|
||||
});
|
||||
|
||||
test.describe('Time List with controlled clock', () => {
|
||||
test.use({
|
||||
clockOptions: {
|
||||
now: getEarliestStartTime(examplePlanSmall3),
|
||||
shouldAdvanceTime: true
|
||||
}
|
||||
});
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('./', { waitUntil: 'domcontentloaded' });
|
||||
});
|
||||
test('Time List shows current events and counts down correctly in real-time mode', async ({
|
||||
page
|
||||
}) => {
|
||||
await test.step('Create a Time List, add a Plan to it, and switch to real-time mode', async () => {
|
||||
// Create Time List
|
||||
const timelist = await createDomainObjectWithDefaults(page, {
|
||||
type: 'Time List'
|
||||
});
|
||||
|
||||
// Create a Plan with events that count down and up.
|
||||
// Add it as a child to the Time List.
|
||||
await createPlanFromJSON(page, {
|
||||
json: examplePlanSmall3,
|
||||
parent: timelist.uuid
|
||||
});
|
||||
|
||||
// Navigate to the Time List in real-time mode
|
||||
await page.goto(
|
||||
`${timelist.url}?tc.mode=local&tc.startDelta=900000&tc.endDelta=1800000&tc.timeSystem=utc&view=grid`
|
||||
);
|
||||
});
|
||||
|
||||
const countUpCells = [
|
||||
getCell(page, 1, TIME_TO_FROM_COLUMN),
|
||||
getCell(page, 2, TIME_TO_FROM_COLUMN)
|
||||
];
|
||||
const countdownCells = [
|
||||
getCell(page, 3, TIME_TO_FROM_COLUMN),
|
||||
getCell(page, 4, TIME_TO_FROM_COLUMN)
|
||||
];
|
||||
|
||||
// Verify that the countdown cells are counting down
|
||||
for (let i = 0; i < countdownCells.length; i++) {
|
||||
await test.step(`Countdown cell ${i + 1} counts down`, async () => {
|
||||
const countdownCell = countdownCells[i];
|
||||
// Get the initial countdown timestamp object
|
||||
const beforeCountdown = await getCountdownObject(page, i + 3);
|
||||
// Wait until it changes
|
||||
await expect(countdownCell).not.toHaveText(beforeCountdown.toString());
|
||||
// Get the new countdown timestamp object
|
||||
const afterCountdown = await getCountdownObject(page, i + 3);
|
||||
// Verify that the new countdown timestamp object is less than the old one
|
||||
expect(Number(afterCountdown.seconds)).toBeLessThan(Number(beforeCountdown.seconds));
|
||||
});
|
||||
}
|
||||
|
||||
// Verify that the countup cells are counting up
|
||||
for (let i = 0; i < countUpCells.length; i++) {
|
||||
await test.step(`Countup cell ${i + 1} counts up`, async () => {
|
||||
const countdownCell = countUpCells[i];
|
||||
// Get the initial countup timestamp object
|
||||
const beforeCountdown = await getCountdownObject(page, i + 1);
|
||||
// Wait until it changes
|
||||
await expect(countdownCell).not.toHaveText(beforeCountdown.toString());
|
||||
// Get the new countup timestamp object
|
||||
const afterCountdown = await getCountdownObject(page, i + 1);
|
||||
// Verify that the new countup timestamp object is greater than the old one
|
||||
expect(Number(afterCountdown.seconds)).toBeGreaterThan(Number(beforeCountdown.seconds));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the cell at the given row and column indices.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {number} rowIndex
|
||||
* @param {number} columnIndex
|
||||
* @returns {import('@playwright/test').Locator} cell
|
||||
*/
|
||||
function getCell(page, rowIndex, columnIndex) {
|
||||
return page.getByRole('cell').nth(rowIndex * NUM_COLUMNS + columnIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the innerText of the cell at the given row and column indices.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {number} rowIndex
|
||||
* @param {number} columnIndex
|
||||
* @returns {Promise<string>} text
|
||||
*/
|
||||
async function getCellText(page, rowIndex, columnIndex) {
|
||||
const text = await getCell(page, rowIndex, columnIndex).innerText();
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the text from the countdown cell in the given row, assert that it matches the countdown
|
||||
* regex, and return an object representing the countdown.
|
||||
* @param {import('@playwright/test').Page} page
|
||||
* @param {number} rowIndex the row index
|
||||
* @returns {Promise<CountdownObject>} countdownObject
|
||||
*/
|
||||
async function getCountdownObject(page, rowIndex) {
|
||||
const timeToFrom = await getCellText(page, HEADER_ROW + rowIndex, TIME_TO_FROM_COLUMN);
|
||||
|
||||
expect(timeToFrom).toMatch(COUNTDOWN_REGEXP);
|
||||
const match = timeToFrom.match(COUNTDOWN_REGEXP);
|
||||
|
||||
return {
|
||||
sign: match[COUNTDOWN.SIGN],
|
||||
days: match[COUNTDOWN.DAYS],
|
||||
hours: match[COUNTDOWN.HOURS],
|
||||
minutes: match[COUNTDOWN.MINUTES],
|
||||
seconds: match[COUNTDOWN.SECONDS],
|
||||
toString: () => timeToFrom
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,18 +23,20 @@
|
||||
const { test } = require('../../pluginFixtures');
|
||||
const {
|
||||
setBoundsToSpanAllActivities,
|
||||
setDraftStatusForPlan
|
||||
setDraftStatusForPlan,
|
||||
getEarliestStartTime
|
||||
} = require('../../helper/planningUtils');
|
||||
const { createDomainObjectWithDefaults, createPlanFromJSON } = require('../../appActions');
|
||||
const percySnapshot = require('@percy/playwright');
|
||||
const VISUAL_URL = require('../../constants').VISUAL_URL;
|
||||
const VISUAL_FIXED_URL = require('../../constants').VISUAL_FIXED_URL;
|
||||
const examplePlanSmall = require('../../test-data/examplePlans/ExamplePlan_Small2.json');
|
||||
const examplePlanSmall3 = require('../../test-data/examplePlans/ExamplePlan_Small3.json');
|
||||
|
||||
const snapshotScope = '.l-shell__pane-main .l-pane__contents';
|
||||
|
||||
test.describe('Visual - Planning', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
||||
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||
});
|
||||
|
||||
test('Plan View', async ({ page, theme }) => {
|
||||
@@ -54,7 +56,7 @@ test.describe('Visual - Planning', () => {
|
||||
name: 'Plan Visual Test (Draft)',
|
||||
json: examplePlanSmall
|
||||
});
|
||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
||||
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||
await setDraftStatusForPlan(page, plan);
|
||||
|
||||
await setBoundsToSpanAllActivities(page, examplePlanSmall, plan.url);
|
||||
@@ -90,7 +92,7 @@ test.describe('Visual - Planning', () => {
|
||||
|
||||
await setDraftStatusForPlan(page, plan);
|
||||
|
||||
await page.goto(VISUAL_URL, { waitUntil: 'domcontentloaded' });
|
||||
await page.goto(VISUAL_FIXED_URL, { waitUntil: 'domcontentloaded' });
|
||||
|
||||
await setBoundsToSpanAllActivities(page, examplePlanSmall, ganttChart.url);
|
||||
await percySnapshot(page, `Gantt Chart View w/ draft status (theme: ${theme})`, {
|
||||
|
||||
@@ -77,9 +77,9 @@ const headerItems = [
|
||||
format: function (value) {
|
||||
let result;
|
||||
if (value < 0) {
|
||||
result = `+${getPreciseDuration(Math.abs(value), true)}`;
|
||||
result = `+${getPreciseDuration(Math.abs(value), true, true)}`;
|
||||
} else if (value > 0) {
|
||||
result = `-${getPreciseDuration(value, true)}`;
|
||||
result = `-${getPreciseDuration(value, true, true)}`;
|
||||
} else {
|
||||
result = 'Now';
|
||||
}
|
||||
@@ -350,8 +350,10 @@ export default {
|
||||
},
|
||||
applyStyles(activities) {
|
||||
let firstCurrentActivityIndex = -1;
|
||||
let activityClosestToNowIndex = -1;
|
||||
let firstFutureActivityIndex = -1;
|
||||
let currentActivitiesCount = 0;
|
||||
let pastActivitiesCount = 0;
|
||||
let futureActivitiesCount = 0;
|
||||
const styledActivities = activities.map((activity, index) => {
|
||||
if (this.timestamp >= activity.start && this.timestamp <= activity.end) {
|
||||
activity.cssClass = CURRENT_CSS_SUFFIX;
|
||||
@@ -363,11 +365,13 @@ export default {
|
||||
} else if (this.timestamp < activity.start) {
|
||||
activity.cssClass = FUTURE_CSS_SUFFIX;
|
||||
//the index of the first activity that's greater than the current timestamp
|
||||
if (activityClosestToNowIndex < 0) {
|
||||
activityClosestToNowIndex = index;
|
||||
if (firstFutureActivityIndex < 0) {
|
||||
firstFutureActivityIndex = index;
|
||||
}
|
||||
futureActivitiesCount = futureActivitiesCount + 1;
|
||||
} else {
|
||||
activity.cssClass = PAST_CSS_SUFFIX;
|
||||
pastActivitiesCount = pastActivitiesCount + 1;
|
||||
}
|
||||
|
||||
if (!activity.key) {
|
||||
@@ -384,9 +388,14 @@ export default {
|
||||
return activity;
|
||||
});
|
||||
|
||||
this.activityClosestToNowIndex = activityClosestToNowIndex;
|
||||
this.firstCurrentActivityIndex = firstCurrentActivityIndex;
|
||||
if (firstCurrentActivityIndex > -1) {
|
||||
this.firstCurrentOrFutureActivityIndex = firstCurrentActivityIndex;
|
||||
} else if (firstFutureActivityIndex > -1) {
|
||||
this.firstCurrentOrFutureActivityIndex = firstFutureActivityIndex;
|
||||
}
|
||||
this.currentActivitiesCount = currentActivitiesCount;
|
||||
this.pastActivitiesCount = pastActivitiesCount;
|
||||
this.futureActivitiesCount = futureActivitiesCount;
|
||||
|
||||
return styledActivities;
|
||||
},
|
||||
@@ -401,9 +410,10 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
this.firstCurrentActivityIndex = -1;
|
||||
this.activityClosestToNowIndex = -1;
|
||||
this.firstCurrentOrFutureActivityIndex = -1;
|
||||
this.pastActivitiesCount = 0;
|
||||
this.currentActivitiesCount = 0;
|
||||
this.futureActivitiesCount = 0;
|
||||
this.$el.parentElement?.scrollTo({ top: 0 });
|
||||
this.autoScrolled = false;
|
||||
},
|
||||
@@ -413,41 +423,48 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
const row = this.$el.querySelector('.js-list-item');
|
||||
if (row && this.firstCurrentActivityIndex > -1) {
|
||||
// scroll to somewhere mid-way of the current activities
|
||||
const ROW_HEIGHT = row.getBoundingClientRect().height;
|
||||
// See #7167 for scrolling algorithm
|
||||
const scrollTop = this.calculateScrollOffset();
|
||||
|
||||
if (this.canAutoScroll() === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollOffset =
|
||||
this.currentActivitiesCount > 0 ? Math.floor(this.currentActivitiesCount / 2) : 0;
|
||||
this.$el.parentElement?.scrollTo({
|
||||
top: ROW_HEIGHT * (this.firstCurrentActivityIndex + scrollOffset),
|
||||
behavior: 'smooth'
|
||||
});
|
||||
this.autoScrolled = false;
|
||||
} else if (row && this.activityClosestToNowIndex > -1) {
|
||||
// scroll to somewhere close to 'now'
|
||||
|
||||
const ROW_HEIGHT = row.getBoundingClientRect().height;
|
||||
|
||||
if (this.canAutoScroll() === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$el.parentElement.scrollTo({
|
||||
top: ROW_HEIGHT * (this.activityClosestToNowIndex - 1),
|
||||
behavior: 'smooth'
|
||||
});
|
||||
this.autoScrolled = false;
|
||||
} else {
|
||||
// scroll to the top
|
||||
if (scrollTop === undefined) {
|
||||
this.resetScroll();
|
||||
} else {
|
||||
this.$el.parentElement?.scrollTo({
|
||||
top: scrollTop,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
this.autoScrolled = false;
|
||||
}
|
||||
},
|
||||
calculateScrollOffset() {
|
||||
let scrollTop;
|
||||
|
||||
//No scrolling necessary if no past events are present
|
||||
if (this.pastActivitiesCount > 0) {
|
||||
const row = this.$el.querySelector('.js-list-item');
|
||||
const ROW_HEIGHT = row.getBoundingClientRect().height;
|
||||
|
||||
const maxViewableActivities =
|
||||
Math.floor(this.$el.parentElement.getBoundingClientRect().height / ROW_HEIGHT) - 1;
|
||||
|
||||
const currentAndFutureActivities = this.currentActivitiesCount + this.futureActivitiesCount;
|
||||
|
||||
//If there is more viewable area than all current and future activities combined, then show some past events
|
||||
const numberOfPastEventsToShow = maxViewableActivities - currentAndFutureActivities;
|
||||
if (numberOfPastEventsToShow > 0) {
|
||||
//some past events can be shown - get that scroll index
|
||||
if (this.pastActivitiesCount > numberOfPastEventsToShow) {
|
||||
scrollTop =
|
||||
ROW_HEIGHT * (this.firstCurrentOrFutureActivityIndex + numberOfPastEventsToShow);
|
||||
}
|
||||
} else {
|
||||
// only show current and future events
|
||||
scrollTop = ROW_HEIGHT * this.firstCurrentOrFutureActivityIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return scrollTop;
|
||||
},
|
||||
deferAutoScroll() {
|
||||
//if this is not a user-triggered event, don't defer auto scrolling
|
||||
if (this.autoScrolled) {
|
||||
|
||||
@@ -63,10 +63,12 @@ export function millisecondsToDHMS(numericDuration) {
|
||||
return `${dhms ? '+' : ''} ${dhms}`;
|
||||
}
|
||||
|
||||
export function getPreciseDuration(value, excludeMilliSeconds) {
|
||||
export function getPreciseDuration(value, excludeMilliSeconds, useDayFormat) {
|
||||
let preciseDuration;
|
||||
const ms = value || 0;
|
||||
|
||||
const duration = [
|
||||
toDoubleDigits(Math.floor(normalizeAge(ms / ONE_DAY))),
|
||||
Math.floor(normalizeAge(ms / ONE_DAY)),
|
||||
toDoubleDigits(Math.floor(normalizeAge((ms % ONE_DAY) / ONE_HOUR))),
|
||||
toDoubleDigits(Math.floor(normalizeAge((ms % ONE_HOUR) / ONE_MINUTE))),
|
||||
toDoubleDigits(Math.floor(normalizeAge((ms % ONE_MINUTE) / ONE_SECOND)))
|
||||
@@ -74,5 +76,20 @@ export function getPreciseDuration(value, excludeMilliSeconds) {
|
||||
if (!excludeMilliSeconds) {
|
||||
duration.push(toTripleDigits(Math.floor(normalizeAge(ms % ONE_SECOND))));
|
||||
}
|
||||
return duration.join(':');
|
||||
|
||||
if (useDayFormat) {
|
||||
// Format days as XD
|
||||
const days = duration.shift();
|
||||
if (days > 0) {
|
||||
preciseDuration = `${days}D ${duration.join(':')}`;
|
||||
} else {
|
||||
preciseDuration = duration.join(':');
|
||||
}
|
||||
} else {
|
||||
const days = toDoubleDigits(duration.shift());
|
||||
duration.unshift(days);
|
||||
preciseDuration = duration.join(':');
|
||||
}
|
||||
|
||||
return preciseDuration;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user