-
Notifications
You must be signed in to change notification settings - Fork 119
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1019 from ga-devfront/feat/progress-tracker-script
[NEW UI] progress tracker script
- Loading branch information
Showing
31 changed files
with
878 additions
and
298 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
export default abstract class ComponentAbstract { | ||
readonly #element: HTMLElement; | ||
|
||
public constructor(element: HTMLElement) { | ||
this.#element = element; | ||
} | ||
|
||
public get element(): HTMLElement { | ||
return this.#element; | ||
} | ||
|
||
protected queryElement = <T extends HTMLElement>(selector: string, errorMessage: string): T => { | ||
const element = (this.element.querySelector(selector) as T) ?? document.querySelector(selector); | ||
if (!element) { | ||
throw new Error(errorMessage); | ||
} | ||
return element; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import ComponentAbstract from './ComponentAbstract'; | ||
|
||
export default class LogsSummary extends ComponentAbstract { | ||
#logsSummaryText = this.queryElement<HTMLDivElement>( | ||
'[data-slot-component="text"]', | ||
'Logs summary text not found' | ||
); | ||
|
||
/** | ||
* @public | ||
* @param text - text summary to display. | ||
* @description Allows to update the summary text of the logs. | ||
*/ | ||
public setLogsSummaryText = (text: string): void => { | ||
this.#logsSummaryText.innerText = text; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
import ComponentAbstract from './ComponentAbstract'; | ||
import { SeverityClasses, LogEntry } from '../types/logsTypes'; | ||
import { parseLogWithSeverity } from '../utils/logsUtils'; | ||
|
||
export default class LogsViewer extends ComponentAbstract { | ||
#warnings: string[] = []; | ||
#errors: string[] = []; | ||
#isSummaryDisplayed: boolean = false; | ||
|
||
#logsList = this.queryElement<HTMLDivElement>( | ||
'[data-slot-component="list"]', | ||
'Logs list not found' | ||
); | ||
#logsScroll = this.queryElement<HTMLDivElement>( | ||
'[data-slot-component="scroll"]', | ||
'Logs scroll not found' | ||
); | ||
#logsSummary = this.queryElement<HTMLDivElement>( | ||
'[data-slot-component="summary"]', | ||
'Logs summary not found' | ||
); | ||
#templateLogLine = this.queryElement<HTMLTemplateElement>( | ||
'#log-line', | ||
'Template log line not found' | ||
); | ||
#templateSummary = this.queryElement<HTMLTemplateElement>( | ||
'#log-summary', | ||
'Template summary not found' | ||
); | ||
|
||
/** | ||
* @public | ||
* @param {string[]} logs - Array of log strings to be added. | ||
* @description Adds logs to the viewer and updates the DOM with log lines. | ||
* Logs with specific severity (WARNING, ERROR) are tracked with unique IDs. | ||
* Prevents adding logs if the summary is already displayed. | ||
*/ | ||
public addLogs = (logs: string[]): void => { | ||
if (this.#isSummaryDisplayed) { | ||
console.warn('Cannot add logs while the summary is displayed'); | ||
return; | ||
} | ||
|
||
const fragment = document.createDocumentFragment(); | ||
|
||
logs.forEach((log) => { | ||
const logEntry = parseLogWithSeverity(log); | ||
const logLine = this.#createLogLine(logEntry); | ||
|
||
if (logEntry.className === SeverityClasses.WARNING) { | ||
const id = `warning-${this.#warnings.length}`; | ||
this.#warnings.push(id); | ||
logLine.id = id; | ||
} | ||
|
||
if (logEntry.className === SeverityClasses.ERROR) { | ||
const id = `error-${this.#errors.length}`; | ||
this.#errors.push(id); | ||
logLine.id = id; | ||
} | ||
|
||
fragment.appendChild(logLine); | ||
}); | ||
|
||
this.#appendFragmentElement(fragment, this.#logsList); | ||
this.#scrollToBottom(); | ||
}; | ||
|
||
/** | ||
* @public | ||
* @description Displays a summary of logs, grouping warnings and errors. | ||
* Summaries include links to the corresponding log lines. | ||
* Adds a click event listener to handle navigation within the summary. | ||
* Prevents displaying a summary if no logs are present. | ||
*/ | ||
public displaySummary(): void { | ||
if (!this.#logsList.hasChildNodes()) { | ||
console.warn('Cannot display summary because logs are empty'); | ||
return; | ||
} | ||
|
||
const fragment = document.createDocumentFragment(); | ||
|
||
if (this.#warnings.length > 0) { | ||
const warningsSummary = this.#createSummary(SeverityClasses.WARNING, this.#warnings); | ||
fragment.appendChild(warningsSummary); | ||
} | ||
|
||
if (this.#errors.length > 0) { | ||
const errorsSummary = this.#createSummary(SeverityClasses.ERROR, this.#errors); | ||
fragment.appendChild(errorsSummary); | ||
} | ||
|
||
if (fragment.hasChildNodes()) { | ||
this.#logsSummary.addEventListener('click', this.#handleLinkEvent); | ||
} | ||
|
||
this.#appendFragmentElement(fragment, this.#logsSummary); | ||
this.#isSummaryDisplayed = true; | ||
} | ||
|
||
/** | ||
* @private | ||
* @param {LogEntry} logEntry - Parsed log entry containing message and severity information. | ||
* @returns {HTMLDivElement} - The created log line element. | ||
* @description Creates an HTML log line element based on the log entry's severity and message. | ||
* Applies appropriate CSS classes and data attributes to the log line. | ||
*/ | ||
#createLogLine = (logEntry: LogEntry): HTMLDivElement => { | ||
const logLineFragment = this.#templateLogLine.content.cloneNode(true) as DocumentFragment; | ||
const logLine = logLineFragment.querySelector('.logs__line') as HTMLDivElement; | ||
|
||
logLine.classList.add(`logs__line--${logEntry.className}`); | ||
logLine.setAttribute('data-status', logEntry.className); | ||
logLine.textContent = logEntry.message; | ||
|
||
return logLine; | ||
}; | ||
|
||
/** | ||
* @private | ||
* @param {DocumentFragment} fragment - The fragment containing child elements to append. | ||
* @param {HTMLElement} element - The target element to which the fragment will be appended. | ||
* @description Appends a document fragment to a specified HTML element in the DOM. | ||
*/ | ||
#appendFragmentElement = (fragment: DocumentFragment, element: HTMLElement) => { | ||
element.appendChild(fragment); | ||
}; | ||
|
||
/** | ||
* @private | ||
* @description Automatically scrolls the logs container to the bottom of the list. | ||
*/ | ||
#scrollToBottom = () => { | ||
this.#logsScroll.scrollTop = this.#logsScroll.scrollHeight; | ||
}; | ||
|
||
/** | ||
* @private | ||
* @param {SeverityClasses} severity - The severity type (e.g., WARNING, ERROR). | ||
* @param {string[]} logs - Array of log IDs to include in the summary. | ||
* @returns {HTMLDivElement} - The created summary element. | ||
* @description Creates a summary element grouping logs by severity. | ||
* Each log in the summary includes a link to its corresponding log line. | ||
*/ | ||
#createSummary(severity: SeverityClasses, logs: string[]): HTMLDivElement { | ||
const summaryFragment = this.#templateSummary.content.cloneNode(true) as DocumentFragment; | ||
const summary = summaryFragment.querySelector('.logs__summary') as HTMLDivElement; | ||
|
||
const title = this.#getSummaryTitle(severity); | ||
const titleContainer = summary.querySelector('[data-slot-template="title"]') as HTMLDivElement; | ||
titleContainer.textContent = title; | ||
|
||
const linkElement = this.#createSummaryLinkElement(severity); | ||
|
||
logs.forEach((logId) => { | ||
const logElement = document.getElementById(logId)!; | ||
const cloneLogElement = logElement.cloneNode(true) as HTMLDivElement; | ||
cloneLogElement.id = ''; | ||
|
||
const linkClone = linkElement.cloneNode(true) as HTMLAnchorElement; | ||
linkClone.href = `#${logId}`; | ||
|
||
cloneLogElement.appendChild(linkClone); | ||
|
||
summary.appendChild(cloneLogElement); | ||
}); | ||
|
||
return summary; | ||
} | ||
|
||
/** | ||
* @private | ||
* @param {SeverityClasses} severity - The severity type (e.g., WARNING, ERROR). | ||
* @returns {string} - The content of the title template. | ||
* @description Retrieves the title template for the given severity type and extracts its content. | ||
*/ | ||
#getSummaryTitle(severity: SeverityClasses): string { | ||
const titleTemplate = this.queryElement<HTMLTemplateElement>( | ||
`#summary-${severity}-title`, | ||
`Summary ${severity} title not found` | ||
); | ||
|
||
const title = titleTemplate.content.cloneNode(true) as HTMLElement; | ||
|
||
return title.textContent!; | ||
} | ||
|
||
/** | ||
* @private | ||
* @param {SeverityClasses} severity - The severity type (e.g., WARNING, ERROR). | ||
* @returns {HTMLAnchorElement} - The created link element. | ||
* @description Creates a link element from the template corresponding to the given severity type. | ||
*/ | ||
#createSummaryLinkElement(severity: SeverityClasses): HTMLAnchorElement { | ||
const linkTemplate = this.queryElement<HTMLTemplateElement>( | ||
`#summary-${severity}-link`, | ||
`Summary ${severity} link not found` | ||
); | ||
|
||
const linkFragment = linkTemplate.content.cloneNode(true) as DocumentFragment; | ||
return linkFragment.querySelector('.link') as HTMLAnchorElement; | ||
} | ||
|
||
/** | ||
* @private | ||
* @param {MouseEvent} event - The click event object. | ||
* @description Handles click events on summary links to scroll to the corresponding log line. | ||
* Highlights the target log line briefly for visual focus. | ||
*/ | ||
#handleLinkEvent = (event: MouseEvent): void => { | ||
const target = event.target as HTMLAnchorElement; | ||
|
||
// Checks if the clicked element is an <a> tag | ||
if (!target || target.tagName !== 'A' || !target.hash) { | ||
return; | ||
} | ||
|
||
event.preventDefault(); | ||
|
||
const logId = target.hash.substring(1); | ||
const targetElement = document.getElementById(logId); | ||
|
||
if (targetElement) { | ||
const scrollTop = targetElement.offsetTop - this.#logsScroll.offsetTop; | ||
this.#logsScroll.scrollTop = scrollTop; | ||
targetElement.classList.add('logs__line--pointed'); | ||
window.setTimeout(() => { | ||
targetElement.classList.remove('logs__line--pointed'); | ||
}, 2000); | ||
} | ||
}; | ||
} |
Oops, something went wrong.