diff --git a/src/Umbraco.Web.UI.Client/src/app.ts b/src/Umbraco.Web.UI.Client/src/app.ts index 428e9cd8b8..96ad45093e 100644 --- a/src/Umbraco.Web.UI.Client/src/app.ts +++ b/src/Umbraco.Web.UI.Client/src/app.ts @@ -6,7 +6,7 @@ import './installer/installer.element'; import './auth/login/login.element'; import './auth/auth-layout.element'; import './backoffice/backoffice.element'; -import './backoffice/node-editor.element'; +import './backoffice/node-editor-layout.element'; import { UmbSectionContext } from './section.context'; @@ -15,7 +15,14 @@ import { customElement } from 'lit/decorators.js'; import { Subscription } from 'rxjs'; import { getInitStatus } from './api/fetcher'; -import { isUmbRouterBeforeEnterEvent, UmbRoute, UmbRouteLocation, UmbRouter, UmbRouterBeforeEnterEvent, umbRouterBeforeEnterEventType } from './core/router'; +import { + isUmbRouterBeforeEnterEvent, + UmbRoute, + UmbRouteLocation, + UmbRouter, + UmbRouterBeforeEnterEvent, + umbRouterBeforeEnterEventType, +} from './core/router'; import { UmbContextProviderMixin } from './core/context'; const routes: Array = [ @@ -70,7 +77,7 @@ export class UmbApp extends UmbContextProviderMixin(LitElement) { private _onBeforeEnter = (event: Event) => { if (!isUmbRouterBeforeEnterEvent(event)) return; this._handleUnauthorizedNavigation(event); - } + }; private _handleUnauthorizedNavigation(event: UmbRouterBeforeEnterEvent) { if (event.to.route.meta.requiresAuth && !this._isAuthorized()) { @@ -100,26 +107,24 @@ export class UmbApp extends UmbContextProviderMixin(LitElement) { this._router.push('/install'); return; } - + if (!this._isAuthorized() || window.location.pathname === '/install') { this._router.push('/login'); } else { - const next = window.location.pathname === '/' ? '/section/Content' : window.location.pathname; + const next = window.location.pathname === '/' ? '/section/Content' : window.location.pathname; this._router.push(next); } this._useLocation(); - } catch (error) { console.log(error); } } - private _useLocation () { + private _useLocation() { this._locationSubscription?.unsubscribe(); - - this._locationSubscription = this._router?.location - .subscribe((location: UmbRouteLocation) => { + + this._locationSubscription = this._router?.location.subscribe((location: UmbRouteLocation) => { if (location.route.alias === 'login') { this._renderView('umb-login'); return; @@ -134,7 +139,7 @@ export class UmbApp extends UmbContextProviderMixin(LitElement) { }); } - _renderView (view: string) { + _renderView(view: string) { if (this._view?.tagName === view.toUpperCase()) return; this._view = document.createElement(view); this.requestUpdate(); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice-main.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice-main.element.ts index 5730dfcf74..d227e00840 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/backoffice-main.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/backoffice-main.element.ts @@ -33,7 +33,7 @@ export class UmbBackofficeMain extends UmbContextConsumerMixin(LitElement) { private _sectionContext?: UmbSectionContext; private _currentSectionSubscription?: Subscription; - constructor () { + constructor() { super(); this.consumeContext('umbSectionContext', (_instance: UmbSectionContext) => { @@ -42,11 +42,10 @@ export class UmbBackofficeMain extends UmbContextConsumerMixin(LitElement) { }); } - private _useCurrentSection () { + private _useCurrentSection() { this._currentSectionSubscription?.unsubscribe(); - this._currentSectionSubscription = this._sectionContext?.getCurrent() - .subscribe(section => { + this._currentSectionSubscription = this._sectionContext?.getCurrent().subscribe((section) => { this._createSectionElement(section); }); } @@ -56,12 +55,12 @@ export class UmbBackofficeMain extends UmbContextConsumerMixin(LitElement) { this._currentSectionSubscription?.unsubscribe(); } - private async _createSectionElement (section: UmbExtensionManifest) { + private async _createSectionElement(section: UmbExtensionManifest) { if (!section) return; // TODO: How do we handle dynamic imports of our files? if (section.js) { - await import(/* @vite-ignore */section.js); + await import(/* @vite-ignore */ section.js); } if (section.elementName) { @@ -70,9 +69,7 @@ export class UmbBackofficeMain extends UmbContextConsumerMixin(LitElement) { } render() { - return html` - ${ this._sectionElement } - `; + return html` ${this._sectionElement} `; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/node-editor-layout.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/node-editor-layout.element.ts new file mode 100644 index 0000000000..76c3ada1a6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/node-editor-layout.element.ts @@ -0,0 +1,88 @@ +import { css, html, LitElement } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement } from 'lit/decorators.js'; + +import '../properties/node-property.element.ts'; +import '../properties/property-editor-text.element.ts'; +import '../properties/property-editor-textarea.element.ts'; + +@customElement('umb-node-editor-layout') +class UmbNodeEditorLayout extends LitElement { + static styles = [ + UUITextStyles, + css` + :host { + display: block; + width: 100%; + height: 100%; + } + + #node-editor { + background-color: var(--uui-color-background); + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + } + + #header { + background-color: var(--uui-color-surface); + width: 100%; + display: flex; + flex: none; + gap: 16px; + align-items: center; + border-bottom: 1px solid var(--uui-color-border); + } + + #content { + padding: var(--uui-size-6); + display: flex; + flex: 1; + flex-direction: column; + gap: 16px; + } + + #footer { + display: flex; + flex: none; + justify-content: end; + align-items: center; + height: 70px; + width: 100%; + gap: 16px; + padding-right: 24px; + border-top: 1px solid var(--uui-color-border); + background-color: var(--uui-color-surface); + box-sizing: border-box; + } + `, + ]; + + private _onSaveAndPublish() { + console.log('Save and publish'); + } + + render() { + return html` +
+ + + + + +
+ `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-node-editor-layout': UmbNodeEditorLayout; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/node-editor.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/node-editor.element.ts deleted file mode 100644 index 6c9a5af9b2..0000000000 --- a/src/Umbraco.Web.UI.Client/src/backoffice/node-editor.element.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { css, html, LitElement } from 'lit'; -import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { customElement } from 'lit/decorators.js'; - -import '../properties/node-property.element.ts'; -import '../properties/property-editor-text.element.ts'; -import '../properties/property-editor-textarea.element.ts'; - -@customElement('umb-node-editor') -class UmbNodeEditor extends LitElement { - static styles = [ - UUITextStyles, - css` - :host { - display: block; - width: 100%; - height: 100%; - } - - #node-editor { - background-color: var(--uui-color-background); - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - } - - #node-editor-top { - background-color: var(--uui-color-surface); - width: 100%; - display: flex; - flex: none; - gap: 16px; - align-items: center; - border-bottom: 1px solid var(--uui-color-border); - } - - #node-editor-top uui-input { - width: 100%; - margin-left: 16px; - } - - #node-editor-top uui-tab-group { - --uui-tab-divider: var(--uui-color-border); - border-left: 1px solid var(--uui-color-border); - flex-wrap: nowrap; - height: 60px; - } - - #node-editor-content { - padding: var(--uui-size-6); - display: flex; - flex: 1; - flex-direction: column; - gap: 16px; - } - - uui-tab { - font-size: 0.8rem; - } - - #node-editor-bottom { - display: flex; - flex: none; - justify-content: end; - align-items: center; - height: 70px; - width: 100%; - gap: 16px; - padding-right: 24px; - border-top: 1px solid var(--uui-color-border); - background-color: var(--uui-color-surface); - box-sizing: border-box; - } - - .property { - display: grid; - grid-template-columns: 200px 600px; - gap: 32px; - } - .property > .property-label > p { - color: var(--uui-color-text-alt); - } - .property uui-input, - .property uui-textarea { - width: 100%; - } - - uui-box hr { - margin-bottom: var(--uui-size-6); - } - - hr { - border: 0; - border-top: 1px solid var(--uui-color-border-alt); - } - `, - ]; - - render() { - return html` -
-
- - - Content - Info - Actions - -
- - - - - -
- - - -
-
-
- Save and preview - Save - Save and publish -
-
- `; - } -} - -declare global { - interface HTMLElementTagNameMap { - 'umb-node-editor': UmbNodeEditor; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/content/content-editor.element.ts b/src/Umbraco.Web.UI.Client/src/content/content-editor.element.ts new file mode 100644 index 0000000000..a2da80d294 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/content/content-editor.element.ts @@ -0,0 +1,95 @@ +import { css, html, LitElement } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement } from 'lit/decorators.js'; + +import '../properties/node-property.element.ts'; +import '../properties/property-editor-text.element.ts'; +import '../properties/property-editor-textarea.element.ts'; +import '../backoffice/node-editor-layout.element.ts'; + +@customElement('umb-content-editor') +class UmbContentEditor extends LitElement { + static styles = [ + UUITextStyles, + css` + :host { + display: block; + width: 100%; + height: 100%; + } + + uui-input { + width: 100%; + margin-left: 16px; + } + + uui-tab-group { + --uui-tab-divider: var(--uui-color-border); + border-left: 1px solid var(--uui-color-border); + flex-wrap: nowrap; + height: 60px; + } + + uui-tab { + font-size: 0.8rem; + } + + uui-box hr { + margin-bottom: var(--uui-size-6); + } + + hr { + border: 0; + /* TODO: Use correct color property */ + border-top: 1px solid #e7e7e7; + } + `, + ]; + + private _onSaveAndPublish() { + console.log('Save and publish'); + } + + private _onSave() { + console.log('Save'); + } + + private _onSaveAndPreview() { + console.log('Save and preview'); + } + + render() { + return html` + + + + Content + Info + Actions + + + + + + +
+ + + +
+ +
+ Save and preview + Save + Save and publish +
+
+ `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-content-editor': UmbContentEditor; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/content/content-section.element.ts b/src/Umbraco.Web.UI.Client/src/content/content-section.element.ts index 195756b124..a9aacb177d 100644 --- a/src/Umbraco.Web.UI.Client/src/content/content-section.element.ts +++ b/src/Umbraco.Web.UI.Client/src/content/content-section.element.ts @@ -1,7 +1,7 @@ import { defineElement } from '@umbraco-ui/uui-base/lib/registration'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { css, html, LitElement } from 'lit'; - +import './content-editor.element'; @defineElement('umb-content-section') export class UmbContentSection extends LitElement { static styles = [ @@ -16,7 +16,7 @@ export class UmbContentSection extends LitElement { ]; render() { - return html``; + return html``; } } diff --git a/src/Umbraco.Web.UI.Client/src/core/context/context-consumer.test.ts b/src/Umbraco.Web.UI.Client/src/core/context/context-consumer.test.ts index 0aa28485c9..3a20144a2e 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context/context-consumer.test.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context/context-consumer.test.ts @@ -19,16 +19,17 @@ describe('UmbContextConsumer', () => { describe('Public API', () => { describe('methods', () => { - it('has a dispatchRequest method', () => { - expect(consumer).to.have.property('dispatchRequest').that.is.a('function'); + it('has a request method', () => { + expect(consumer).to.have.property('request').that.is.a('function'); }); }); describe('events', () => { - it('dispatches request context event when constructed', async () => { + it('dispatches context request event when constructed', async () => { const listener = oneEvent(window, umbContextRequestEventType); - // eslint-disable-next-line @typescript-eslint/no-empty-function - new UmbContextConsumer(document.body, testContextKey, () => {}); + + consumer.attach(); + const event = await listener as unknown as UmbContextRequestEventImplementation; expect(event).to.exist; expect(event.type).to.eq(umbContextRequestEventType); @@ -39,14 +40,16 @@ describe('UmbContextConsumer', () => { it('works with UmbContextProvider', (done: any) => { const provider = new UmbContextProvider(document.body, testContextKey, new MyClass()); + provider.attach(); const element = document.createElement('div'); document.body.appendChild(element); - new UmbContextConsumer(element, testContextKey, (_instance) => { + const localConsumer = new UmbContextConsumer(element, testContextKey, (_instance) => { expect(_instance.prop).to.eq('value from provider'); done(); - }); + }) + localConsumer.attach(); provider.detach(); }); diff --git a/src/Umbraco.Web.UI.Client/src/core/context/context-consumer.ts b/src/Umbraco.Web.UI.Client/src/core/context/context-consumer.ts index 877af11028..6e16eff06b 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context/context-consumer.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context/context-consumer.ts @@ -9,13 +9,13 @@ export class UmbContextConsumer { /** * Creates an instance of UmbContextConsumer. - * @param {HTMLElement} element + * @param {EventTarget} target * @param {string} _contextKey * @param {UmbContextCallback} _callback * @memberof UmbContextConsumer */ constructor ( - protected element: HTMLElement, + protected target: EventTarget, private _contextKey: string, private _callback: UmbContextCallback ) { @@ -27,7 +27,7 @@ export class UmbContextConsumer { */ public request() { const event = new UmbContextRequestEventImplementation(this._contextKey, this._callback); - this.element.dispatchEvent(event); + this.target.dispatchEvent(event); } public attach() { diff --git a/src/Umbraco.Web.UI.Client/src/core/context/context-provide.event.test.ts b/src/Umbraco.Web.UI.Client/src/core/context/context-provide.event.test.ts index 402a06670b..67c04c81e7 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context/context-provide.event.test.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context/context-provide.event.test.ts @@ -17,7 +17,7 @@ describe('UmbContextProvideEvent', () => { expect(event.composed).to.be.true; }); - it('is cancelable', () => { - expect(event.composed).to.be.false; + it('is not cancelable', () => { + expect(event.cancelable).to.be.false; }); }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/core/context/context-provider.mixin.test.ts b/src/Umbraco.Web.UI.Client/src/core/context/context-provider.mixin.test.ts index acd55e82bd..aeea0e585d 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context/context-provider.mixin.test.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context/context-provider.mixin.test.ts @@ -1,11 +1,12 @@ import { expect, fixture, html } from '@open-wc/testing'; +import { UmbContextProvider } from './context-provider'; import { UmbContextProviderMixin } from './context-provider.mixin'; class MyClass { prop: string; constructor(text: string) { - this.prop = text + this.prop = text; } } @@ -20,8 +21,14 @@ class MyTestProviderElement extends UmbContextProviderMixin(HTMLElement) { customElements.define('my-test-provider-element', MyTestProviderElement); describe('UmbContextProviderMixin', async () => { - const element: MyTestProviderElement = await fixture(html``); - const _providers = (element as any)['_providers']; + + let element: MyTestProviderElement; + let _providers: Map; + + beforeEach(async () => { + element = await fixture(html``); + _providers = (element as any)['_providers']; + }); it('sets all providers to element', () => { expect(_providers.has('my-test-context-1')).to.be.true; @@ -32,8 +39,8 @@ describe('UmbContextProviderMixin', async () => { const provider = _providers.get('my-test-context-1'); expect(provider).to.not.be.undefined; if (!provider) return; - expect(provider['_instance'].prop).to.eq('context value 1'); + expect((provider['_instance'] as MyClass).prop).to.eq('context value 1'); element.provideContext('my-test-context-1', new MyClass('new context value 1')); - expect(provider['_instance'].prop).to.eq('context value 1'); + expect((provider['_instance'] as MyClass).prop).to.eq('context value 1'); }); }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/core/context/context-provider.test.ts b/src/Umbraco.Web.UI.Client/src/core/context/context-provider.test.ts index 24c25cb050..5899817f91 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context/context-provider.test.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context/context-provider.test.ts @@ -12,6 +12,7 @@ describe('UmbContextProvider', () => { beforeEach(() => { provider = new UmbContextProvider(document.body, 'my-test-context', new MyClass()); + provider.attach(); }); afterEach(async () => { @@ -38,7 +39,7 @@ describe('UmbContextProvider', () => { }); it('handles context request events', (done) => { - const event = new UmbContextRequestEventImplementation('my-test-context', (_instance) => { + const event = new UmbContextRequestEventImplementation('my-test-context', (_instance: MyClass) => { expect(_instance.prop).to.eq('value from provider'); done(); }); @@ -50,9 +51,11 @@ describe('UmbContextProvider', () => { const element = document.createElement('div'); document.body.appendChild(element); - new UmbContextConsumer(element, 'my-test-context', (_instance) => { + const localConsumer = new UmbContextConsumer(element, 'my-test-context', (_instance: MyClass) => { expect(_instance.prop).to.eq('value from provider'); done(); + localConsumer.detach(); }); + localConsumer.attach(); }); }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/core/context/context-provider.ts b/src/Umbraco.Web.UI.Client/src/core/context/context-provider.ts index a2118711c1..346deb313b 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context/context-provider.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context/context-provider.ts @@ -6,18 +6,18 @@ import { UmbContextProvideEventImplementation } from './context-provide.event'; * @class UmbContextProvider */ export class UmbContextProvider { - protected host: HTMLElement; + protected host: EventTarget; private _contextKey: string; - private _instance: any; + private _instance: unknown; /** * Creates an instance of UmbContextProvider. - * @param {HTMLElement} host + * @param {EventTarget} host * @param {string} contextKey * @param {*} instance * @memberof UmbContextProvider */ - constructor (host: HTMLElement, contextKey: string, instance: unknown) { + constructor (host: EventTarget, contextKey: string, instance: unknown) { this.host = host; this._contextKey = contextKey; this._instance = instance; @@ -36,7 +36,7 @@ export class UmbContextProvider { */ public detach () { this.host.removeEventListener(umbContextRequestEventType, this._handleContextRequest); - // TODO: fire unprovide event. + // TODO: fire unprovided event. } /** diff --git a/src/Umbraco.Web.UI.Client/src/core/context/context-request.event.test.ts b/src/Umbraco.Web.UI.Client/src/core/context/context-request.event.test.ts index 68eb9780bc..f10e1ff88c 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context/context-request.event.test.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context/context-request.event.test.ts @@ -25,6 +25,6 @@ describe('UmbContextRequestEvent', () => { }); it('is cancelable', () => { - expect(event.composed).to.be.true; + expect(event.cancelable).to.be.true; }); }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/properties/node-property.element.ts b/src/Umbraco.Web.UI.Client/src/properties/node-property.element.ts index 78ce523098..708cf8a4b0 100644 --- a/src/Umbraco.Web.UI.Client/src/properties/node-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/properties/node-property.element.ts @@ -22,15 +22,6 @@ class UmbNodeProperty extends LitElement { .property uui-textarea { width: 100%; } - - uui-box hr { - margin-bottom: var(--uui-size-6); - } - - hr { - border: 0; - border-top: 1px solid var(--uui-color-border-alt); - } `, ];