Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add event handlers of HTML through addEventListener #62

Merged
merged 7 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { readFile } from 'node:fs/promises'
*/
export abstract class BaseComponent<Props = undefined> {
declare $props: Props
eventHandlers?: string[]
#cachedStyles?: string
#cachedScript?: string

Expand Down
15 changes: 15 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ export function stripAnsi(value: string) {
return value.replace(ANSI_REGEX, '')
}

/**
* Return line to add event handler of an element as a listener
*/
export function getAddEventListenerLine({
id,
handler,
event = 'click',
}: {
id: string
handler: string
event?: string
}) {
return `document.getElementById("${id}").addEventListener("${event}",${handler});`
}

/**
* ANSI coloring library
*/
Expand Down
5 changes: 5 additions & 0 deletions src/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ export class Templates {
scripts.push(`<script id="${name}-script"${cspNonceAttr}>${bucket}</script>`)
})

scripts.push(
`<script id="event-handlers-script"${cspNonceAttr}>${this.#knownTemplates.header.eventHandlers?.join('') || ''}${this.#knownTemplates.errorStack.eventHandlers?.join('') || ''}</script>`
)

return { styles: `${styles.join('\n')}\n${injectedStyles}`, scripts: scripts.join('\n') }
}

Expand Down Expand Up @@ -199,6 +203,7 @@ export class Templates {
})
const cause = await this.#tmplToHTML('errorCause', props)
const metadata = await this.#tmplToHTML('errorMetadata', props)

return `${header}${info}${stackTrace}${cause}${metadata}`
},
})
Expand Down
53 changes: 41 additions & 12 deletions src/templates/error_stack/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { dump as dumpCli } from '@poppinss/dumper/console'

import { publicDirURL } from '../../public_dir.js'
import { BaseComponent } from '../../component.js'
import { htmlEscape, colors } from '../../helpers.js'
import { htmlEscape, colors, getAddEventListenerLine } from '../../helpers.js'
import type { ErrorStackProps } from '../../types.js'

const CHEVIRON = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" width="24" height="24" stroke-width="2">
Expand Down Expand Up @@ -41,7 +41,16 @@ const EDITORS: Record<string, string> = {
export class ErrorStack extends BaseComponent<ErrorStackProps> {
cssFile = new URL('./error_stack/style.css', publicDirURL)
scriptFile = new URL('./error_stack/script.js', publicDirURL)

eventHandlers = [
getAddEventListenerLine({
id: 'formatted-frames',
handler: `function(){showFormattedFrames(this)}`,
}),
getAddEventListenerLine({
id: 'raw-frames',
handler: `function(){showRawFrames(this)}`,
}),
]
/**
* Returns the file's relative name from the CWD
*/
Expand Down Expand Up @@ -91,7 +100,7 @@ export class ErrorStack extends BaseComponent<ErrorStackProps> {
/**
* Returns the HTML fragment for the frame location
*/
#renderFrameLocation(frame: StackFrame, id: string, ide: string) {
#renderFrameLocation(frame: StackFrame, id: string, ide: string, index: number) {
const { text, href } = this.#getEditorLink(ide, frame)

const fileName = `<a ${href ? `href="${href}"` : ''} class="stack-frame-filepath" title="${text}">
Expand All @@ -107,7 +116,16 @@ export class ErrorStack extends BaseComponent<ErrorStackProps> {
const loc = `<span>at line <code>${frame.lineNumber}:${frame.columnNumber}</code></span>`

if (frame.type !== 'native' && frame.source) {
return `<button class="stack-frame-location" onclick="toggleFrameSource(event, '${id}')">
const locationId = `stack-frame-location-${index}`

this.eventHandlers.push(
getAddEventListenerLine({
id: locationId,
handler: `function(event){toggleFrameSource(event,'${id}')}`,
})
)

return `<button class="stack-frame-location" id="${locationId}">
${fileName} ${functionName} ${loc}
</button>`
}
Expand All @@ -126,19 +144,30 @@ export class ErrorStack extends BaseComponent<ErrorStackProps> {
expandAtIndex: number,
props: ErrorStackProps
) {
const id = `frame-${index + 1}`
const frameIndex = index + 1
const id = `frame-${frameIndex}`
const label = frame.type === 'app' ? '<span class="frame-label">In App</span>' : ''
const expandedClass = expandAtIndex === index ? 'expanded' : ''
const toggleButton =
frame.type !== 'native' && frame.source
? `<button class="stack-frame-toggle-indicator" onclick="toggleFrameSource(event, '${id}')">
let toggleButton = ''

if (frame.type !== 'native' && frame.source) {
const toggleButtonId = `stack-frame-toggle-indicator-${index}`

this.eventHandlers.push(
getAddEventListenerLine({
id: toggleButtonId,
handler: `function(event){toggleFrameSource(event,'${id}')}`,
})
)

toggleButton = `<button class="stack-frame-toggle-indicator" id="${toggleButtonId}">
${CHEVIRON}
</button>`
: ''
}

return `<li class="stack-frame ${expandedClass} stack-frame-${frame.type}" id="${id}">
<div class="stack-frame-contents">
${this.#renderFrameLocation(frame, id, props.ide)}
${this.#renderFrameLocation(frame, id, props.ide, frameIndex)}
<div class="stack-frame-extras">
${label}
${toggleButton}
Expand Down Expand Up @@ -204,8 +233,8 @@ export class ErrorStack extends BaseComponent<ErrorStackProps> {
</div>
<div>
<div class="toggle-switch">
<button onclick="showFormattedFrames(this)" class="active"> Pretty </button>
<button onclick="showRawFrames(this)"> Raw </button>
<button id="formatted-frames" class="active"> Pretty </button>
<button id="raw-frames"> Raw </button>
</div>
</div>
</div>
Expand Down
15 changes: 14 additions & 1 deletion src/templates/header/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import { BaseComponent } from '../../component.js'
import { getAddEventListenerLine } from '../../helpers.js'
import { publicDirURL } from '../../public_dir.js'
import type { ComponentSharedProps } from '../../types.js'

Expand All @@ -23,6 +24,18 @@ export class Header extends BaseComponent<ComponentSharedProps> {
cssFile = new URL('./header/style.css', publicDirURL)
scriptFile = new URL('./header/script.js', publicDirURL)

/**
* List of header event handlers to add in the generated HTML
* and respond to user actions
*/
eventHandlers = [
getAddEventListenerLine({
id: 'toggle-theme-checkbox',
handler: 'function(){toggleTheme(this)}',
event: 'change',
}),
]

/**
* The toHTML method is used to output the HTML for the
* web view
Expand All @@ -31,7 +44,7 @@ export class Header extends BaseComponent<ComponentSharedProps> {
return `<header id="header">
<div id="header-actions">
<div id="toggle-theme-container">
<input type="checkbox" id="toggle-theme-checkbox" onchange="toggleTheme(this)" />
<input type="checkbox" id="toggle-theme-checkbox" />
<label id="toggle-theme-label" for="toggle-theme-checkbox">
<span id="light-theme-indicator" title="Light mode">${LIGHT_MODE_SVG}</span>
<span id="dark-theme-indicator" title="Dark mode">${DARK_MODE_SVG}</span>
Expand Down
3 changes: 3 additions & 0 deletions tests/templates.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ test.group('Templates', () => {
expect(window.document.querySelector('#error-message')?.textContent?.trim()).toEqual(
'Something went wrong'
)
expect(window.document.querySelector('#event-handlers-script')?.textContent?.trim()).toEqual(
'document.getElementById("toggle-theme-checkbox").addEventListener("change",function(){toggleTheme(this)});document.getElementById("formatted-frames").addEventListener("click",function(){showFormattedFrames(this)});document.getElementById("raw-frames").addEventListener("click",function(){showRawFrames(this)});document.getElementById("stack-frame-toggle-indicator-0").addEventListener("click",function(event){toggleFrameSource(event,\'frame-1\')});document.getElementById("stack-frame-location-1").addEventListener("click",function(event){toggleFrameSource(event,\'frame-1\')});'
)
expect(window.document.querySelector('#error-hint')).toEqual(null)
})

Expand Down