Merge branch 'main' into feature/property-editor-multi-url-picker
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
"runem.lit-plugin",
|
||||
"esbenp.prettier-vscode",
|
||||
"hbenl.vscode-test-explorer",
|
||||
"vunguyentuan.vscode-css-variables"
|
||||
"vunguyentuan.vscode-css-variables",
|
||||
"unifiedjs.vscode-mdx"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { UmbContextToken } from '../context-token';
|
||||
|
||||
export const umbContextRequestEventType = 'umb:context-request';
|
||||
export const umbDebugContextEventType = 'umb:debug-contexts';
|
||||
|
||||
export type UmbContextCallback<T> = (instance: T) => void;
|
||||
|
||||
@@ -31,3 +32,10 @@ export class UmbContextRequestEventImplementation<T = unknown> extends Event imp
|
||||
export const isUmbContextRequestEvent = (event: Event): event is UmbContextRequestEventImplementation => {
|
||||
return event.type === umbContextRequestEventType;
|
||||
};
|
||||
|
||||
|
||||
export class UmbContextDebugRequest extends Event {
|
||||
public constructor(public readonly callback:any) {
|
||||
super(umbDebugContextEventType, { bubbles: true, composed: true, cancelable: false });
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { umbContextRequestEventType, isUmbContextRequestEvent } from '../consume/context-request.event';
|
||||
import { umbContextRequestEventType, isUmbContextRequestEvent, umbDebugContextEventType } from '../consume/context-request.event';
|
||||
import { UmbContextToken } from '../context-token';
|
||||
import { UmbContextProvideEventImplementation } from './context-provide.event';
|
||||
|
||||
@@ -40,6 +40,9 @@ export class UmbContextProvider<HostType extends EventTarget = EventTarget> {
|
||||
public hostConnected() {
|
||||
this.host.addEventListener(umbContextRequestEventType, this._handleContextRequest);
|
||||
this.host.dispatchEvent(new UmbContextProvideEventImplementation(this._contextAlias));
|
||||
|
||||
// Listen to our debug event 'umb:debug-contexts'
|
||||
this.host.addEventListener(umbDebugContextEventType, this._handleDebugContextRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,6 +66,20 @@ export class UmbContextProvider<HostType extends EventTarget = EventTarget> {
|
||||
event.callback(this.#instance);
|
||||
};
|
||||
|
||||
private _handleDebugContextRequest = (event: any) => {
|
||||
// If the event doesn't have an instances property, create it.
|
||||
if(!event.instances){
|
||||
event.instances = new Map();
|
||||
}
|
||||
|
||||
// If the event doesn't have an instance for this context, add it.
|
||||
// Nearest to the DOM element of <umb-debug> will be added first
|
||||
// as contexts can change/override deeper in the DOM
|
||||
if(!event.instances.has(this._contextAlias)){
|
||||
event.instances.set(this._contextAlias, this.#instance);
|
||||
}
|
||||
};
|
||||
|
||||
destroy(): void {
|
||||
// I want to make sure to call this, but for now it was too overwhelming to require the destroy method on context instances.
|
||||
(this.#instance as any).destroy?.();
|
||||
|
||||
@@ -19,6 +19,7 @@ import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { tryExecuteAndNotify } from '@umbraco-cms/resources';
|
||||
import { OpenAPI, RuntimeLevelModel, ServerResource } from '@umbraco-cms/backend-api';
|
||||
import { UmbIconStore } from '@umbraco-cms/store';
|
||||
import { UmbContextDebugRequest, umbDebugContextEventType } from '@umbraco-cms/context-api';
|
||||
|
||||
@customElement('umb-app')
|
||||
export class UmbApp extends UmbLitElement {
|
||||
@@ -83,6 +84,16 @@ export class UmbApp extends UmbLitElement {
|
||||
await this._setInitStatus();
|
||||
await this._registerExtensionManifestsFromServer();
|
||||
this._redirect();
|
||||
|
||||
// Listen for the debug event from the <umb-debug> component
|
||||
this.addEventListener(umbDebugContextEventType, (event: any) => {
|
||||
// Once we got to the outter most component <umb-app>
|
||||
// we can send the event containing all the contexts
|
||||
// we have collected whilst coming up through the DOM
|
||||
// and pass it back down to the callback in
|
||||
// the <umb-debug> component that originally fired the event
|
||||
event.callback(event.instances);
|
||||
});
|
||||
}
|
||||
|
||||
private async _setup() {
|
||||
|
||||
@@ -16,11 +16,7 @@ export class UmbModalLayoutFieldsViewerElement extends UmbModalLayoutElement<Sea
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background-color: var(--uui-color-surface);
|
||||
box-shadow: var(--uui-shadow-depth-1, 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24));
|
||||
border-radius: var(--uui-border-radius);
|
||||
padding: var(--uui-size-space-5);
|
||||
box-sizing: border-box;
|
||||
|
||||
}
|
||||
|
||||
span {
|
||||
|
||||
@@ -133,6 +133,7 @@ export class UmbDashboardPublishedStatusElement extends UmbLitElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<umb-debug enabled dialog></umb-debug>
|
||||
<uui-box headline="Published Cache Status">
|
||||
<p>${this._publishedStatusText}</p>
|
||||
<uui-button
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { css, html, nothing, TemplateResult } from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { UmbContextDebugRequest } from '@umbraco-cms/context-api';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal';
|
||||
|
||||
@customElement('umb-debug')
|
||||
export class UmbDebug extends UmbLitElement {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
#container {
|
||||
display: block;
|
||||
font-family: monospace;
|
||||
|
||||
z-index: 10000;
|
||||
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
uui-badge {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
uui-icon {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.events {
|
||||
background-color: var(--uui-color-danger);
|
||||
color: var(--uui-color-selected-contrast);
|
||||
max-height: 0;
|
||||
transition: max-height 0.25s ease-out;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.events.open {
|
||||
max-height: 500px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.events > div {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@property({ reflect: true, type: Boolean })
|
||||
enabled = false;
|
||||
|
||||
@property({ reflect: true, type: Boolean })
|
||||
dialog = false;
|
||||
|
||||
@property()
|
||||
contexts = new Map();
|
||||
|
||||
@state()
|
||||
private _debugPaneOpen = false;
|
||||
|
||||
private _modalService?: UmbModalService;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.consumeContext(UMB_MODAL_SERVICE_CONTEXT_TOKEN, (modalService) => {
|
||||
this._modalService = modalService;
|
||||
});
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
// Dispatch it
|
||||
this.dispatchEvent(
|
||||
new UmbContextDebugRequest((contexts: Map<any, any>) => {
|
||||
// The Contexts are collected
|
||||
// When travelling up through the DOM from this element
|
||||
// to the root of <umb-app> which then uses the callback prop
|
||||
// of the this event tha has been raised to assign the contexts
|
||||
// back to this property of the WebComponent
|
||||
this.contexts = contexts;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.enabled) {
|
||||
return this.dialog ? this._renderDialog() : this._renderPanel();
|
||||
} else {
|
||||
return nothing;
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleDebugPane() {
|
||||
this._debugPaneOpen = !this._debugPaneOpen;
|
||||
}
|
||||
|
||||
private async _openDialog() {
|
||||
// Open a modal that uses the HTML component called 'umb-debug-modal-layout'
|
||||
await import('./debug.modal.element.js');
|
||||
this._modalService?.open('umb-debug-modal-layout', {
|
||||
size: 'small',
|
||||
type: 'sidebar',
|
||||
data: {
|
||||
content: this._renderContextAliases(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _renderDialog() {
|
||||
return html` <div id="container">
|
||||
<uui-badge color="danger" look="primary" attention @click="${this._openDialog}">
|
||||
<uui-icon name="umb:bug"></uui-icon> Debug
|
||||
</uui-badge>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private _renderPanel() {
|
||||
return html` <div id="container">
|
||||
<uui-button color="danger" look="primary" @click="${this._toggleDebugPane}">
|
||||
<uui-icon name="umb:bug"></uui-icon>
|
||||
Debug
|
||||
</uui-button>
|
||||
|
||||
<div class="events ${this._debugPaneOpen ? 'open' : ''}">
|
||||
<div>
|
||||
<ul>
|
||||
${this._renderContextAliases()}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
private _renderContextAliases() {
|
||||
const contextsTemplates: TemplateResult[] = [];
|
||||
|
||||
for (const [alias, instance] of this.contexts) {
|
||||
contextsTemplates.push(
|
||||
html` <li>
|
||||
Context: <strong>${alias}</strong>
|
||||
<em>(${typeof instance})</em>
|
||||
<ul>
|
||||
${this._renderInstance(instance)}
|
||||
</ul>
|
||||
</li>`
|
||||
);
|
||||
}
|
||||
|
||||
return contextsTemplates;
|
||||
}
|
||||
|
||||
private _renderInstance(instance: any) {
|
||||
const instanceTemplates: TemplateResult[] = [];
|
||||
|
||||
// TODO: WB - Maybe make this a switch statement?
|
||||
if (typeof instance === 'function') {
|
||||
return instanceTemplates.push(html`<li>Callable Function</li>`);
|
||||
} else if (typeof instance === 'object') {
|
||||
const methodNames = this.getClassMethodNames(instance);
|
||||
if (methodNames.length) {
|
||||
instanceTemplates.push(
|
||||
html`
|
||||
<li>
|
||||
<strong>Methods</strong>
|
||||
<ul>
|
||||
${methodNames.map((methodName) => html`<li>${methodName}</li>`)}
|
||||
</ul>
|
||||
</li>
|
||||
`
|
||||
);
|
||||
}
|
||||
|
||||
const props: TemplateResult[] = [];
|
||||
|
||||
for (const key in instance) {
|
||||
if (key.startsWith('_')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = instance[key];
|
||||
if (typeof value === 'string') {
|
||||
props.push(html`<li>${key} = ${value}</li>`);
|
||||
} else {
|
||||
props.push(html`<li>${key} <em>(${typeof value})</em></li>`);
|
||||
}
|
||||
}
|
||||
|
||||
instanceTemplates.push(html`
|
||||
<li>
|
||||
<strong>Properties</strong>
|
||||
<ul>
|
||||
${props}
|
||||
</ul>
|
||||
</li>
|
||||
`);
|
||||
} else {
|
||||
instanceTemplates.push(html`<li>Context is a primitive with value: ${instance}</li>`);
|
||||
}
|
||||
|
||||
return instanceTemplates;
|
||||
}
|
||||
|
||||
private getClassMethodNames(klass: any) {
|
||||
const isGetter = (x: any, name: string): boolean => !!(Object.getOwnPropertyDescriptor(x, name) || {}).get;
|
||||
const isFunction = (x: any, name: string): boolean => typeof x[name] === 'function';
|
||||
const deepFunctions = (x: any): any =>
|
||||
x !== Object.prototype &&
|
||||
Object.getOwnPropertyNames(x)
|
||||
.filter((name) => isGetter(x, name) || isFunction(x, name))
|
||||
.concat(deepFunctions(Object.getPrototypeOf(x)) || []);
|
||||
const distinctDeepFunctions = (klass: any) => Array.from(new Set(deepFunctions(klass)));
|
||||
|
||||
const allMethods =
|
||||
typeof klass.prototype === 'undefined'
|
||||
? distinctDeepFunctions(klass)
|
||||
: Object.getOwnPropertyNames(klass.prototype);
|
||||
return allMethods.filter((name: any) => name !== 'constructor' && !name.startsWith('_'));
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-debug': UmbDebug;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { css, html, TemplateResult } from 'lit';
|
||||
import { customElement } from 'lit/decorators.js';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css';
|
||||
import { UmbModalLayoutElement } from '@umbraco-cms/modal';
|
||||
|
||||
export interface UmbDebugModalData {
|
||||
content: TemplateResult | string;
|
||||
}
|
||||
|
||||
@customElement('umb-debug-modal-layout')
|
||||
export default class UmbDebugModalLayout extends UmbModalLayoutElement<UmbDebugModalData> {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
uui-dialog-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
padding: var(--uui-size-space-5);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
uui-scroll-container {
|
||||
overflow-y: scroll;
|
||||
max-height: 100%;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
uui-icon {
|
||||
vertical-align: text-top;
|
||||
color: var(--uui-color-danger);
|
||||
}
|
||||
|
||||
.context {
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid var(--uui-color-danger-emphasis);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
h3 > span {
|
||||
border-radius: var(--uui-size-4);
|
||||
background-color: var(--uui-color-danger);
|
||||
color: var(--uui-color-danger-contrast);
|
||||
padding: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-top: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
private _handleClose() {
|
||||
this.modalHandler?.close();
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-dialog-layout>
|
||||
<span slot="headline"> <uui-icon name="umb:bug"></uui-icon> Debug: Contexts </span>
|
||||
<uui-scroll-container id="field-settings"> ${this.data?.content} </uui-scroll-container>
|
||||
<uui-button slot="actions" look="primary" label="Close sidebar" @click="${this._handleClose}">Close</uui-button>
|
||||
</uui-dialog-layout>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-debug-modal-layout': UmbDebugModalLayout;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { Canvas, Meta, Story } from '@storybook/addon-docs';
|
||||
import DebugDialogImage from './umb-debug-dialog.jpg';
|
||||
import DebugImage from './umb-debug.jpg';
|
||||
|
||||
<Meta title="Guides/Debug" parameters={{ previewTabs: { canvas: { hidden: true } } }} />
|
||||
|
||||
|
||||
|
||||
|
||||
# Debugging
|
||||
|
||||
## Debugging Contexts
|
||||
|
||||
The component `<umb-debug>` allows you to discover the available contexts from the current DOM element, that you are able to consume and use.
|
||||
|
||||
For example it will help you as a package developer or implementor to know you are able to consume the `DigalogService` and quickly see what properties and methods are available to use.
|
||||
|
||||
This can help with the developer experience to quickly see what is available to use and how to use it.
|
||||
|
||||
### Usage
|
||||
The `<umb-debug>` component can be used in two different ways, either as a button or as a dialog. By default it is rendered as a button and the debug information about available contexts is dissplayed inline to where the element is placed.
|
||||
<img src={DebugImage} width="100%"/>
|
||||
|
||||
```typescript
|
||||
// This will add a Debug button to the UI and once clicked the information about avilable contextes will slide down
|
||||
<umb-debug enabled></umb-debug>
|
||||
```
|
||||
|
||||
#### Dialog
|
||||
This example uses an additional property/attribute `dialog` which adds a smaller badge to the UI as opposed to a button and will open the information in a small dialog/modal from the right hand side, this may be more useful to use when space is limited in the UI to add a button and pane of information directly to where the element is placed.
|
||||
<img src={DebugDialogImage} width="100%"/>
|
||||
|
||||
```typescript
|
||||
// This will open the debug information in a small dialog/modal from the right hand side
|
||||
<umb-debug enabled dialog></umb-debug>
|
||||
```
|
||||
|
||||
#### Disable
|
||||
You may wish to temporarily hide or disable the debug information but return to it later on in the development process.
|
||||
|
||||
```typescript
|
||||
// To hide or remove the button ensure you remove the enabled attribute or set the enabled property to false
|
||||
<umb-debug></umb-debug>
|
||||
```
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 226 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 174 KiB |
@@ -33,3 +33,6 @@ import './input-checkbox-list/input-checkbox-list.element';
|
||||
import './input-multi-url-picker/input-multi-url-picker.element';
|
||||
|
||||
import './empty-state/empty-state.element';
|
||||
|
||||
import './debug/debug.element';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user