Browse Source

feat(admin-ui): Improve dev mode extension point display

Michael Bromley 2 years ago
parent
commit
4678930362

+ 10 - 1
packages/admin-ui/src/lib/catalog/src/components/collection-data-table/collection-data-table.component.html

@@ -26,7 +26,16 @@
                     [class.expand]="column.expand"
                 >
                     <div class="cell-content" [ngClass]="column.align">
-                        <span>{{ column.heading }}</span>
+                        <vdr-ui-extension-point
+                            [locationId]="id"
+                            [metadata]="column.id"
+                            api="dataTable"
+                            [topPx]="-6"
+                            [leftPx]="-24"
+                            display="block"
+                        >
+                            <span>{{ column.heading }}</span>
+                        </vdr-ui-extension-point>
                         <div *ngIf="column.sort as sort" class="sort-toggle">
                             <button (click)="sort.toggleSortOrder()" [class.active]="sort.sortOrder">
                                 <clr-icon *ngIf="!sort.sortOrder" shape="two-way-arrows left"></clr-icon>

+ 5 - 1
packages/admin-ui/src/lib/core/src/common/component-registry-types.ts

@@ -1,4 +1,5 @@
 import { FormControl } from '@angular/forms';
+import { DataTableLocationId } from '../shared/components/data-table-2/data-table-custom-component.service';
 
 /**
  * @description
@@ -131,4 +132,7 @@ export type CustomDetailComponentLocationId =
     | 'tax-rate-detail'
     | 'zone-detail';
 
-export type UIExtensionLocationId = ActionBarLocationId | CustomDetailComponentLocationId;
+export type UIExtensionLocationId =
+    | ActionBarLocationId
+    | CustomDetailComponentLocationId
+    | DataTableLocationId;

+ 31 - 4
packages/admin-ui/src/lib/core/src/components/app-shell/app-shell.component.html

@@ -1,7 +1,9 @@
 <div class="app-container" [dir]="direction$ | async">
     <div class="left-nav" [class.expanded]="mainNavExpanded$ | async">
         <div class="branding">
-            <a [routerLink]="['/']" *ngIf="!hideVendureBranding"><img src="assets/logo-top.webp" class="logo" style="max-width: 100px" /></a>
+            <a [routerLink]="['/']" *ngIf="!hideVendureBranding"
+                ><img src="assets/logo-top.webp" class="logo" style="max-width: 100px"
+            /></a>
             <div class="collapse-menu">
                 <button class="" (click)="collapseNav()">
                     <clr-icon shape="window-close" size="24"></clr-icon>
@@ -18,7 +20,21 @@
             <hr />
             <vdr-main-nav displayMode="settings" (itemClick)="collapseNav()"></vdr-main-nav>
         </div>
-        <div class="mx-2"></div>
+        <div class="mx-2">
+            <div *ngIf="devMode" class="flex center mb-2">
+                <vdr-dropdown>
+                    <button class="icon-button dev-mode-button" vdrDropdownTrigger title="DEV MODE">
+                        <clr-icon shape="code" size="24"></clr-icon> DEV MODE
+                    </button>
+                    <vdr-dropdown-menu>
+                        <div class="px-2 py-1">
+                            <div>Version: {{ version }}</div>
+                            <div>View UI extension points: <kbd>CTRL + U</kbd></div>
+                        </div>
+                    </vdr-dropdown-menu>
+                </vdr-dropdown>
+            </div>
+        </div>
     </div>
 
     <div class="surface">
@@ -26,8 +42,19 @@
             <div class="top-bar">
                 <div class="expand-menu mr-1">
                     <button class="" (click)="expandNav()">
-                        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="bars">
-                          <path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
+                        <svg
+                            xmlns="http://www.w3.org/2000/svg"
+                            fill="none"
+                            viewBox="0 0 24 24"
+                            stroke-width="1.5"
+                            stroke="currentColor"
+                            class="bars"
+                        >
+                            <path
+                                stroke-linecap="round"
+                                stroke-linejoin="round"
+                                d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
+                            />
                         </svg>
                     </button>
                 </div>

+ 5 - 0
packages/admin-ui/src/lib/core/src/components/app-shell/app-shell.component.scss

@@ -145,3 +145,8 @@
     display: block;
     width: 100%;
 }
+
+.dev-mode-button {
+    opacity: 0.7;
+    font-size: 12px;
+}

+ 4 - 1
packages/admin-ui/src/lib/core/src/components/app-shell/app-shell.component.ts

@@ -1,10 +1,11 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, isDevMode, OnInit } from '@angular/core';
 import { Router } from '@angular/router';
 import { EMPTY, Observable } from 'rxjs';
 import { map, switchMap, take } from 'rxjs/operators';
 
 import { getAppConfig } from '../../app.config';
 import { LanguageCode } from '../../common/generated-types';
+import { ADMIN_UI_VERSION } from '../../common/version';
 import { DataService } from '../../data/providers/data.service';
 import { AuthService } from '../../providers/auth/auth.service';
 import { BreadcrumbService } from '../../providers/breadcrumb/breadcrumb.service';
@@ -19,6 +20,7 @@ import { UiLanguageSwitcherDialogComponent } from '../ui-language-switcher-dialo
     styleUrls: ['./app-shell.component.scss'],
 })
 export class AppShellComponent implements OnInit {
+    version = ADMIN_UI_VERSION;
     userName$: Observable<string>;
     uiLanguageAndLocale$: Observable<[LanguageCode, string | undefined]>;
     direction$: Observable<'ltr' | 'rtl'>;
@@ -26,6 +28,7 @@ export class AppShellComponent implements OnInit {
     hideVendureBranding = getAppConfig().hideVendureBranding;
     pageTitle$: Observable<string>;
     mainNavExpanded$: Observable<boolean>;
+    devMode = isDevMode();
 
     constructor(
         private authService: AuthService,

+ 1 - 1
packages/admin-ui/src/lib/core/src/components/main-nav/main-nav.component.html

@@ -13,7 +13,7 @@
                 <vdr-ui-extension-point
                     [locationId]="section.id"
                     api="navMenu"
-                    [topPx]="-6"
+                    [topPx]="0"
                     [leftPx]="8"
                     display="block"
                 >

+ 1 - 1
packages/admin-ui/src/lib/core/src/shared/components/action-bar-items/action-bar-items.component.html

@@ -1,4 +1,4 @@
-<vdr-ui-extension-point [locationId]="locationId" api="actionBar">
+<vdr-ui-extension-point [locationId]="locationId" api="actionBar" [leftPx]="-24">
     <ng-container *ngFor="let item of items$ | async">
         <button
             *vdrIfPermissions="item.requiresPermission"

+ 10 - 1
packages/admin-ui/src/lib/core/src/shared/components/data-table-2/data-table2.component.html

@@ -23,7 +23,16 @@
                     [class.expand]="column.expand"
                 >
                     <div class="cell-content" [ngClass]="column.align">
-                        <span>{{ column.heading }}</span>
+                        <vdr-ui-extension-point
+                            [locationId]="id"
+                            [metadata]="column.id"
+                            api="dataTable"
+                            [topPx]="-6"
+                            [leftPx]="-24"
+                            display="block"
+                        >
+                            <span>{{ column.heading }}</span>
+                        </vdr-ui-extension-point>
                         <div *ngIf="column.sort as sort" class="sort-toggle">
                             <button (click)="sort.toggleSortOrder()" [class.active]="sort.sortOrder">
                                 <clr-icon *ngIf="!sort.sortOrder" shape="two-way-arrows left"></clr-icon>

+ 4 - 24
packages/admin-ui/src/lib/core/src/shared/components/ui-extension-point/ui-extension-point.component.html

@@ -1,33 +1,13 @@
 <div [class.highlight]="isDevMode && (display$ | async)" class="wrapper" [style.display]="display">
-    <vdr-dropdown *ngIf="isDevMode && (display$ | async)">
-        <button class="btn btn-icon btn-link extension-point-info-trigger"
+    <vdr-dropdown *ngIf="isDevMode && (display$ | async)" #dropdownComponent>
+        <button class="extension-point-info-trigger"
                 [style.top.px]="topPx ?? 0"
                 [style.left.px]="leftPx ?? 0"
                 vdrDropdownTrigger>
-            <clr-icon shape="plugin" class="" size="16"></clr-icon>
+            <clr-icon shape="plugin" class="" size="12"></clr-icon>
         </button>
         <vdr-dropdown-menu>
-            <div class="extension-info">
-                <pre *ngIf="api === 'actionBar'">
-addActionBarItem({{ '{' }}
-  id: 'my-button',
-  label: 'My Action',
-  locationId: '{{ locationId }}',
-{{ '}' }})</pre>
-                <pre *ngIf="api === 'navMenu'">
-addNavMenuItem({{ '{' }}
-  id: 'my-menu-item',
-  label: 'My Menu Item',
-  routerLink: ['/extensions/my-plugin'],
-  {{ '}' }},
-  '{{ locationId }}'
-)</pre>
-                <pre *ngIf="api === 'detailComponent'">
-registerCustomDetailComponent({{ '{' }}
-  locationId: '{{ locationId }}',
-  component: MyCustomComponent,
-{{ '}' }})</pre>
-            </div>
+            <div #editor contenteditable="false" class="highlighted"></div>
         </vdr-dropdown-menu>
     </vdr-dropdown>
     <ng-content></ng-content>

+ 39 - 3
packages/admin-ui/src/lib/core/src/shared/components/ui-extension-point/ui-extension-point.component.scss

@@ -1,4 +1,4 @@
-@import "variables";
+@import 'variables';
 
 :host {
     position: relative;
@@ -8,13 +8,27 @@
     height: 100%;
 }
 
-.extension-point-info-trigger {
+button.extension-point-info-trigger {
     position: absolute;
+    background-color: var(--color-accent-500);
+    border-radius: 50%;
+    width: 22px;
+    height: 22px;
+    border: 0;
     margin: 0;
     padding: 0;
     z-index: 100;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    opacity: 0.8;
+
+    &:hover {
+        opacity: 1;
+    }
+
     clr-icon {
-        color: var(--color-success-500);
+        color: white;
     }
 }
 
@@ -27,3 +41,25 @@ pre {
     font-family: 'Source Code Pro', 'Lucida Console', Monaco, monospace;
     background-color: var(--color-grey-200);
 }
+
+::ng-deep .highlighted {
+    margin: calc(var(--space-unit) * 2);
+    padding: calc(var(--space-unit) * 2);
+    border: 1px solid var(--color-grey-300);
+    border-radius: var(--border-radius);
+    background-color: var(--color-component-bg-200);
+    font-family: 'Source Code Pro', 'Lucida Console', Monaco, monospace;
+    color: var(--color-json-editor-text);
+    .ts-string {
+        color: var(--color-accent-600);
+    }
+    .ts-comment {
+        color: var(--color-grey-500);
+    }
+    .ts-keyword {
+        color: var(--color-json-editor-key);
+    }
+    .ts-default {
+        color: var(--color-json-editor-text);
+    }
+}

+ 117 - 4
packages/admin-ui/src/lib/core/src/shared/components/ui-extension-point/ui-extension-point.component.ts

@@ -1,8 +1,23 @@
-import { ChangeDetectionStrategy, Component, HostBinding, Input, isDevMode, OnInit } from '@angular/core';
+import {
+    AfterViewInit,
+    ChangeDetectionStrategy,
+    Component,
+    ElementRef,
+    HostBinding,
+    Input,
+    isDevMode,
+    OnInit,
+    ViewChild,
+} from '@angular/core';
 import { Observable } from 'rxjs';
+import { CodeJar } from 'codejar';
+import { tap } from 'rxjs/operators';
 
 import { UIExtensionLocationId } from '../../../common/component-registry-types';
 import { DataService } from '../../../data/providers/data.service';
+import { DropdownComponent } from '../dropdown/dropdown.component';
+
+type UiExtensionType = 'actionBar' | 'navMenu' | 'detailComponent' | 'dataTable';
 
 @Component({
     selector: 'vdr-ui-extension-point',
@@ -10,21 +25,119 @@ import { DataService } from '../../../data/providers/data.service';
     styleUrls: ['./ui-extension-point.component.scss'],
     changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class UiExtensionPointComponent implements OnInit {
+export class UiExtensionPointComponent implements OnInit, AfterViewInit {
     @Input() locationId: UIExtensionLocationId;
+    @Input() metadata?: any;
     @Input() topPx: number;
     @Input() leftPx: number;
     @HostBinding('style.display')
     @Input()
     display: 'block' | 'inline-block' = 'inline-block';
-    @Input() api: 'actionBar' | 'navMenu' | 'detailComponent';
+    @Input() api: UiExtensionType;
+    @ViewChild('editor') private editorElementRef: ElementRef<HTMLDivElement>;
+    @ViewChild('dropdownComponent') private dropdownComponent: DropdownComponent;
     display$: Observable<boolean>;
+    jar: CodeJar;
     readonly isDevMode = isDevMode();
+
     constructor(private dataService: DataService) {}
 
+    getCodeTemplate(api: UiExtensionType): string {
+        return codeTemplates[api](this.locationId, this.metadata).trim();
+    }
+
     ngOnInit(): void {
         this.display$ = this.dataService.client
             .uiState()
-            .mapStream(({ uiState }) => uiState.displayUiExtensionPoints);
+            .mapStream(({ uiState }) => uiState.displayUiExtensionPoints)
+            .pipe(
+                tap(display => {
+                    if (display) {
+                        setTimeout(() => {
+                            const highlight = (editor: HTMLElement) => {
+                                const code = editor.textContent ?? '';
+                                editor.innerHTML = highlightTsCode(this.getCodeTemplate(this.api));
+                            };
+                            this.editorElementRef.nativeElement.contentEditable = 'false';
+                            this.jar = CodeJar(this.editorElementRef.nativeElement, highlight);
+                            this.jar.updateCode(this.getCodeTemplate(this.api));
+                        });
+                    }
+                }),
+            );
+    }
+
+    ngAfterViewInit() {
+        // this.dropdownComponent.onOpenChange(isOpen => {
+        //     if (isOpen) {
+        //     }
+        // });
     }
 }
+
+function highlightTsCode(tsCode: string) {
+    tsCode = tsCode.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
+
+    return tsCode.replace(
+        /\b(abstract|any|as|boolean|break|case|catch|class|const|continue|default|do|else|enum|export|extends|false|finally|for|from|function|get|if|implements|import|in|instanceof|interface|is|keyof|let|module|namespace|never|new|null|number|object|of|private|protected|public|readonly|require|return|set|static|string|super|switch|symbol|this|throw|true|try|type|typeof|undefined|var|void|while|with|yield)\b|\/\/.*|\/\*[\s\S]*?\*\/|"(?:\\[\s\S]|[^\\"])*"|'[^']*'/g,
+        (match, ...args) => {
+            if (/^"/.test(match) || /^'/.test(match)) {
+                return '<span class="ts-string">' + match + '</span>';
+            } else if (/\/\/.*|\/\*[\s\S]*?\*\//.test(match)) {
+                return '<span class="ts-comment">' + match + '</span>';
+            } else if (
+                /\b(abstract|any|as|boolean|break|case|catch|class|const|continue|default|do|else|enum|export|extends|false|finally|for|from|function|get|if|implements|import|in|instanceof|interface|is|keyof|let|module|namespace|never|new|null|number|object|of|private|protected|public|readonly|require|return|set|static|string|super|switch|symbol|this|throw|true|try|type|typeof|undefined|var|void|while|with|yield)\b/.test(
+                    match,
+                )
+            ) {
+                return '<span class="ts-keyword">' + match + '</span>';
+            } else {
+                return '<span class="ts-default">' + match + '</span>';
+            }
+        },
+    );
+}
+
+const codeTemplates: Record<UiExtensionType, (locationId: UIExtensionLocationId, metadata: any) => string> = {
+    actionBar: locationId => `
+import { addActionBarItem } from '@vendure/admin-ui/core';
+
+export default [
+  addActionBarItem({
+    id: 'my-button',
+    label: 'My Action',
+    locationId: '${locationId}',
+  });
+]`,
+    navMenu: locationId => `
+import { addNavMenuSection } from '@vendure/admin-ui/core';
+
+export default [
+  addNavMenuSection({
+      id: 'my-menu-item',
+      label: 'My Menu Item',
+      routerLink: ['/extensions/my-plugin'],
+    }
+    '${locationId}'
+  );
+]`,
+    detailComponent: locationId => `
+import { registerCustomDetailComponent } from '@vendure/admin-ui/core';
+
+export default [
+  registerCustomDetailComponent({
+    locationId: '${locationId}',
+    component: MyCustomComponent,
+  });
+]`,
+    dataTable: (locationId, metadata) => `
+import { registerDataTableComponent } from '@vendure/admin-ui/core';
+
+export default [
+  registerDataTableComponent({
+    tableId: '${locationId}',
+    columnId: '${metadata}',
+    component: MyCustomComponent,
+  });
+]`,
+};