From fe32ef093f8b9e6b944c0742de6d6ce8cdbe7bee Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 20 Aug 2024 12:40:11 +0200 Subject: [PATCH 01/13] limit UI when readonly --- .../input-document/input-document.element.ts | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts index 6c07e8834a..f7febe5dd3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts @@ -1,5 +1,14 @@ import { UmbDocumentPickerContext } from './input-document.context.js'; -import { classMap, css, customElement, html, property, repeat, state } from '@umbraco-cms/backoffice/external/lit'; +import { + classMap, + css, + customElement, + html, + nothing, + property, + repeat, + state, +} from '@umbraco-cms/backoffice/external/lit'; import { splitStringToArray } from '@umbraco-cms/backoffice/utils'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; @@ -103,6 +112,15 @@ export class UmbInputDocumentElement extends UmbFormControlMixin 0 ? this.selection.join(',') : undefined; } + /** + * Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content. + * @type {boolean} + * @attr + * @default false + */ + @property({ type: Boolean, reflect: true }) + readonly = false; + @state() private _editDocumentPath = ''; @@ -173,7 +191,8 @@ export class UmbInputDocumentElement extends UmbFormControlMixin + label=${this.localize.term('general_choose')} + ?disabled=${this.readonly}> `; } @@ -193,11 +212,14 @@ export class UmbInputDocumentElement extends UmbFormControlMixin + ${this.#renderIcon(item)} ${this.#renderIsTrashed(item)} - ${this.#renderOpenButton(item)} - this.#onRemove(item)} label=${this.localize.term('general_remove')}> + ${this.#renderOpenButton(item)} ${this.#renderRemoveButton(item)} `; @@ -213,8 +235,16 @@ export class UmbInputDocumentElement extends UmbFormControlMixinTrashed`; } + #renderRemoveButton(item: UmbDocumentItemModel) { + if (this.readonly) return nothing; + return html` + this.#onRemove(item)} label=${this.localize.term('general_remove')}> + `; + } + #renderOpenButton(item: UmbDocumentItemModel) { - if (!this.showOpenButton) return; + if (this.readonly) return nothing; + if (!this.showOpenButton) return nothing; return html` Date: Tue, 20 Aug 2024 12:59:50 +0200 Subject: [PATCH 02/13] enable and disable the sorter based on readonly state --- .../components/input-document/input-document.element.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts index f7febe5dd3..bb96ad3ea3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts @@ -119,7 +119,14 @@ export class UmbInputDocumentElement extends UmbFormControlMixin Date: Tue, 20 Aug 2024 12:59:56 +0200 Subject: [PATCH 03/13] add jsdocs --- .../src/packages/core/sorter/sorter.controller.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index 7afe497382..a188b37f85 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -294,6 +294,11 @@ export class UmbSorterController Date: Tue, 20 Aug 2024 14:20:27 +0200 Subject: [PATCH 04/13] remove draggable attribute from element when sorter is disabled --- .../src/packages/core/sorter/sorter.controller.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index a188b37f85..72e7855b73 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -260,6 +260,8 @@ export class UmbSorterController(); + public get identifier() { return this.#config.identifier; } @@ -313,6 +315,7 @@ export class UmbSorterController { const containerEl = (this.#config.containerSelector @@ -376,6 +381,7 @@ export class UmbSorterController this.destroyItem(item)); } _itemDraggedOver = (e: DragEvent) => { @@ -453,6 +461,9 @@ export class UmbSorterController x !== element); } #setupPlaceholderStyle() { From 0f7e461a9aff1b29cf42ea74ff9a9821c71795c8 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 21 Aug 2024 11:36:50 +0200 Subject: [PATCH 05/13] remove console log --- .../src/packages/core/sorter/sorter.controller.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index 72e7855b73..3520456400 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -315,7 +315,6 @@ export class UmbSorterController Date: Wed, 21 Aug 2024 11:56:11 +0200 Subject: [PATCH 06/13] start on sorter controller tests --- .../core/sorter/sorter.controller.test.ts | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts new file mode 100644 index 0000000000..5ce872340b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts @@ -0,0 +1,82 @@ +import { UmbSorterController } from './sorter.controller.js'; +import { aTimeout, expect, fixture, html } from '@open-wc/testing'; +import { customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '../lit-element/lit-element.element.js'; + +@customElement('test-my-sorter') +class UmbSorterTestElement extends UmbLitElement { + model: Array = ['1', '2', '3']; + + sorter = new UmbSorterController(this, { + getUniqueOfElement: (element) => { + return element.id; + }, + getUniqueOfModel: (modelEntry) => { + return modelEntry; + }, + identifier: 'Umb.SorterIdentifier.Test', + itemSelector: 'li', + containerSelector: 'ul', + onChange: ({ model }) => { + this.model = model; + }, + }); + + getAllItems() { + return Array.from(this.shadowRoot!.querySelectorAll('li')); + } + + override render() { + return html`
    +
  • Item 1
  • +
  • Item 2
  • +
  • Item 3
  • +
`; + } +} + +describe('UmbSorterController', () => { + let element: UmbSorterTestElement; + + beforeEach(async () => { + element = await fixture(html``); + await aTimeout(10); + }); + + it('is defined with its own instance', () => { + expect(element).to.be.instanceOf(UmbSorterTestElement); + }); + + describe('Set up', () => { + it('should find all items', () => { + const items = element.getAllItems(); + expect(items.length).to.equal(3); + }); + + it('sets all allowed draggable items to draggable', () => { + const items = element.getAllItems(); + items.forEach((item) => { + expect(item.draggable).to.be.true; + }); + }); + }); + + describe('disable', () => { + it('sets all items to non draggable', () => { + element.sorter.disable(); + const items = element.getAllItems(); + items.forEach((item) => { + expect(item.draggable).to.be.false; + }); + }); + }); + + describe('enable', () => { + it('sets all items to draggable', () => { + const items = element.getAllItems(); + items.forEach((item) => { + expect(item.draggable).to.be.true; + }); + }); + }); +}); From b5d3f84e38ec7a4946aa3c81ea81e0258dd1d052 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 21 Aug 2024 12:47:10 +0200 Subject: [PATCH 07/13] add tests for disabled items --- .../core/sorter/sorter.controller.test.ts | 54 ++++++++++++++----- 1 file changed, 41 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts index 5ce872340b..1bb49a8c9a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts @@ -5,7 +5,7 @@ import { UmbLitElement } from '../lit-element/lit-element.element.js'; @customElement('test-my-sorter') class UmbSorterTestElement extends UmbLitElement { - model: Array = ['1', '2', '3']; + model: Array = ['1', '2', '3', '4']; sorter = new UmbSorterController(this, { getUniqueOfElement: (element) => { @@ -15,23 +15,33 @@ class UmbSorterTestElement extends UmbLitElement { return modelEntry; }, identifier: 'Umb.SorterIdentifier.Test', - itemSelector: 'li', - containerSelector: 'ul', + itemSelector: '.item', + containerSelector: '#container', + disabledItemSelector: '.disabled', onChange: ({ model }) => { this.model = model; }, }); getAllItems() { - return Array.from(this.shadowRoot!.querySelectorAll('li')); + return Array.from(this.shadowRoot!.querySelectorAll('.item')) as HTMLElement[]; + } + + getSortableItems() { + return Array.from(this.shadowRoot!.querySelectorAll('.item:not(.disabled')) as HTMLElement[]; + } + + getDisabledItems() { + return Array.from(this.shadowRoot!.querySelectorAll('.item.disabled')) as HTMLElement[]; } override render() { - return html`
    -
  • Item 1
  • -
  • Item 2
  • -
  • Item 3
  • -
`; + return html`
+
Item 1
+
Item 2
+
Item 3
+
Item 4
+
`; } } @@ -50,15 +60,24 @@ describe('UmbSorterController', () => { describe('Set up', () => { it('should find all items', () => { const items = element.getAllItems(); - expect(items.length).to.equal(3); + expect(items.length).to.equal(4); }); it('sets all allowed draggable items to draggable', () => { - const items = element.getAllItems(); + const items = element.getSortableItems(); + expect(items.length).to.equal(3); items.forEach((item) => { expect(item.draggable).to.be.true; }); }); + + it('sets all disabled items non draggable', () => { + const items = element.getDisabledItems(); + expect(items.length).to.equal(1); + items.forEach((item) => { + expect(item.draggable).to.be.false; + }); + }); }); describe('disable', () => { @@ -72,11 +91,20 @@ describe('UmbSorterController', () => { }); describe('enable', () => { - it('sets all items to draggable', () => { - const items = element.getAllItems(); + it('sets all allowed items to draggable', () => { + const items = element.getSortableItems(); + expect(items.length).to.equal(3); items.forEach((item) => { expect(item.draggable).to.be.true; }); }); + + it('sets all disabled items non draggable', () => { + const items = element.getDisabledItems(); + expect(items.length).to.equal(1); + items.forEach((item) => { + expect(item.draggable).to.be.false; + }); + }); }); }); From 77b68e8d03cea822c36d7b16540ed59e2015e26b Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 21 Aug 2024 13:07:01 +0200 Subject: [PATCH 08/13] Update sorter.controller.test.ts --- .../core/sorter/sorter.controller.test.ts | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts index 1bb49a8c9a..4b68007e03 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts @@ -55,9 +55,70 @@ describe('UmbSorterController', () => { it('is defined with its own instance', () => { expect(element).to.be.instanceOf(UmbSorterTestElement); + expect(element.sorter).to.be.instanceOf(UmbSorterController); }); - describe('Set up', () => { + describe('Public API', () => { + describe('methods', () => { + it('has a enable method', () => { + expect(element.sorter).to.have.property('enable').that.is.a('function'); + }); + + it('has a disable method', () => { + expect(element.sorter).to.have.property('disable').that.is.a('function'); + }); + + it('has a setModel method', () => { + expect(element.sorter).to.have.property('setModel').that.is.a('function'); + }); + + it('has a hasItem method', () => { + expect(element.sorter).to.have.property('hasItem').that.is.a('function'); + }); + + it('has a getItem method', () => { + expect(element.sorter).to.have.property('getItem').that.is.a('function'); + }); + + it('has a setupItem method', () => { + expect(element.sorter).to.have.property('setupItem').that.is.a('function'); + }); + + it('has a destroyItem method', () => { + expect(element.sorter).to.have.property('destroyItem').that.is.a('function'); + }); + + it('has a hasOtherItemsThan method', () => { + expect(element.sorter).to.have.property('hasOtherItemsThan').that.is.a('function'); + }); + + it('has a moveItemInModel method', () => { + expect(element.sorter).to.have.property('moveItemInModel').that.is.a('function'); + }); + + it('has a updateAllowIndication method', () => { + expect(element.sorter).to.have.property('updateAllowIndication').that.is.a('function'); + }); + + it('has a removeAllowIndication method', () => { + expect(element.sorter).to.have.property('removeAllowIndication').that.is.a('function'); + }); + + it('has a notifyDisallowed method', () => { + expect(element.sorter).to.have.property('notifyDisallowed').that.is.a('function'); + }); + + it('has a notifyRequestDrop method', () => { + expect(element.sorter).to.have.property('notifyRequestDrop').that.is.a('function'); + }); + + it('has a destroy method', () => { + expect(element.sorter).to.have.property('destroy').that.is.a('function'); + }); + }); + }); + + describe('Init', () => { it('should find all items', () => { const items = element.getAllItems(); expect(items.length).to.equal(4); From 92931c3770f6c66f85a33253e455ec098f242eae Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 21 Aug 2024 13:23:14 +0200 Subject: [PATCH 09/13] add getModel method --- .../src/packages/core/sorter/sorter.controller.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts index 3520456400..3fb39166e1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.ts @@ -329,6 +329,15 @@ export class UmbSorterController} + * @memberof UmbSorterController + */ + getModel(): Array { + return this.#model; + } + hasItem(unique: UniqueType) { return this.#model.find((x) => this.#config.getUniqueOfModel(x) === unique) !== undefined; } From d40ee05bdaf76e64490c87d750787147f64fe72b Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 21 Aug 2024 13:23:27 +0200 Subject: [PATCH 10/13] add tests for model methods --- .../core/sorter/sorter.controller.test.ts | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts index 4b68007e03..3595ec1376 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/sorter/sorter.controller.test.ts @@ -5,8 +5,6 @@ import { UmbLitElement } from '../lit-element/lit-element.element.js'; @customElement('test-my-sorter') class UmbSorterTestElement extends UmbLitElement { - model: Array = ['1', '2', '3', '4']; - sorter = new UmbSorterController(this, { getUniqueOfElement: (element) => { return element.id; @@ -168,4 +166,40 @@ describe('UmbSorterController', () => { }); }); }); + + describe('setModel & getModel', () => { + it('it sets the model', () => { + const model = ['1', '2', '3', '4']; + element.sorter.setModel(model); + expect(element.sorter.getModel()).to.deep.equal(model); + }); + }); + + describe('hasItem', () => { + beforeEach(() => { + element.sorter.setModel(['1', '2', '3', '4']); + }); + + it('returns true if item exists', () => { + expect(element.sorter.hasItem('1')).to.be.true; + }); + + it('returns false if item does not exist', () => { + expect(element.sorter.hasItem('5')).to.be.false; + }); + }); + + describe('getItem', () => { + beforeEach(() => { + element.sorter.setModel(['1', '2', '3', '4']); + }); + + it('returns the item if it exists', () => { + expect(element.sorter.getItem('1')).to.equal('1'); + }); + + it('returns undefined if item does not exist', () => { + expect(element.sorter.getItem('5')).to.be.undefined; + }); + }); }); From 363ec8c48d8524a58f824813765695acf4f7d840 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 21 Aug 2024 13:29:35 +0200 Subject: [PATCH 11/13] Update manifests.ts --- .../documents/property-editors/document-picker/manifests.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/manifests.ts index cfff881f56..93493b2efe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/manifests.ts @@ -12,6 +12,7 @@ export const manifests: Array = [ propertyEditorSchemaAlias: 'Umbraco.ContentPicker', icon: 'icon-document', group: 'pickers', + supportsReadOnly: true, settings: { properties: [ { From a244700dcccfe1e98c216185a8dd0020bca2fbf9 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 21 Aug 2024 13:31:37 +0200 Subject: [PATCH 12/13] pass readonly to input --- .../property-editor-ui-document-picker.element.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts index 2d7dfbd6f4..5b85243363 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts @@ -26,6 +26,15 @@ export class UmbPropertyEditorUIDocumentPickerElement extends UmbLitElement impl this._showOpenButton = config.getValueByAlias('showOpenButton') ?? false; } + /** + * Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content. + * @type {boolean} + * @attr + * @default false + */ + @property({ type: Boolean, reflect: true }) + readonly = false; + @state() private _min = 0; @@ -57,7 +66,8 @@ export class UmbPropertyEditorUIDocumentPickerElement extends UmbLitElement impl .startNode=${startNode} .value=${this.value} ?showOpenButton=${this._showOpenButton} - @change=${this.#onChange}> + @change=${this.#onChange} + ?readonly=${this.readonly}> `; } From 1dcbed5bb110400aff21cb2549446b824eb56cce Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 21 Aug 2024 13:50:24 +0200 Subject: [PATCH 13/13] make linter happy --- .../components/input-document/input-document.element.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts index bb96ad3ea3..4c24fa30a3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts @@ -124,7 +124,12 @@ export class UmbInputDocumentElement extends UmbFormControlMixin