Merge branch 'main' into feature/property-editor-multi-url-picker

This commit is contained in:
Lone Iversen
2023-02-20 12:18:43 +01:00
committed by GitHub
12 changed files with 399 additions and 7 deletions

View File

@@ -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"
]
}

View File

@@ -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 });
}
}

View File

@@ -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?.();

View File

@@ -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() {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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';