/* * This file belongs to Hoist, an application development toolkit * developed by Extremely Heavy Industries (www.xh.io | info@xh.io) * * Copyright © 2026 Extremely Heavy Industries Inc. */ import {ExceptionDialogModel} from '@xh/hoist/appcontainer/ExceptionDialogModel'; import {div, filler, fragment} from '@xh/hoist/cmp/layout'; import {hoistCmp, uses, XH} from '@xh/hoist/core'; import {button} from '@xh/hoist/desktop/cmp/button'; import {toolbar} from '@xh/hoist/desktop/cmp/toolbar'; import {Icon} from '@xh/hoist/icon'; import {dialog, dialogBody} from '@xh/hoist/kit/blueprint'; import './ExceptionDialog.scss'; import {exceptionDialogDetails} from './ExceptionDialogDetails'; /** * Dialog for display of exceptions, with support for viewing a detailed stacktrace * and an option to force reload of the application (in the case of a fatal exception). * @internal */ export const exceptionDialog = hoistCmp.factory({ displayName: 'Exception Dialog', model: uses(ExceptionDialogModel), render({model}) { const {exception, options} = model; if (!exception) return null; const onClose = !options.requireReload ? () => model.close() : null; return fragment( dialog({ isOpen: true, title: options.title, className: 'xh-exception-dialog', isCloseButtonShown: !options.requireReload, onClose, icon: Icon.warning(), items: [ dialogBody(options.message), div({ omit: !exception.traceId || exception.isRoutine, className: 'xh-exception-dialog__trace-id', item: `Trace ID: ${exception.traceId}` }), bbar() ] }), exceptionDialogDetails() ); } }); //-------------------------------- // Implementation //-------------------------------- const bbar = hoistCmp.factory(({model}) => toolbar( button({ omit: !XH.identityService?.isImpersonating, icon: Icon.impersonate(), text: 'End Impersonation', testId: 'xh-exception-end-impersonation-btn', onClick: () => XH.identityService.endImpersonateAsync() }), filler(), button({ icon: Icon.search(), text: 'Show/Report Details', testId: 'xh-exception-details-btn', onClick: () => model.openDetails(), omit: !model.options.showAsError }), dismissButton() ) ); /** * A Dismiss button that either forces reload, or allows close. * @internal */ export const dismissButton = hoistCmp.factory(({model}) => { const reloadRequired = model.options.requireReload; return reloadRequired ? button({ icon: Icon.refresh(), text: 'Reload App', testId: 'xh-exception-dismiss-btn', autoFocus: true, onClick: () => XH.reloadApp({removeQueryParams: true}) }) : button({ text: 'Close', testId: 'xh-exception-dismiss-btn', autoFocus: true, onClick: () => model.close() }); });