diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-properties.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-properties.element.ts index aa2c091c78..623c65fb5c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-properties.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-properties.element.ts @@ -20,7 +20,6 @@ const SORTER_CONFIG: UmbSorterConfig = { querySelectModelToElement: (container: HTMLElement, modelEntry: DocumentTypePropertyTypeResponseModel) => { return container.querySelector('data-umb-property-id=[' + modelEntry.id + ']'); }, - placeholderClass: 'select', identifier: 'content-type-property-sorter', itemSelector: '[data-umb-property-id]', disabledItemSelector: '[inherited]', @@ -35,19 +34,15 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle let sortOrder = 0; if (this._propertyStructure.length > 0) { if (args.newIndex === 0) { - // TODO: Remove 'as any' when sortOrder is added to the model: - sortOrder = ((this._propertyStructure[0] as any).sortOrder ?? 0) - 1; + sortOrder = (this._propertyStructure[0].sortOrder ?? 0) - 1; } else { sortOrder = - ((this._propertyStructure[Math.min(args.newIndex, this._propertyStructure.length - 1)] as any).sortOrder ?? - 0) + 1; + (this._propertyStructure[Math.min(args.newIndex, this._propertyStructure.length - 1)].sortOrder ?? 0) + 1; } } - console.log('perform insert', sortOrder, args.item.id); return this._propertyStructureHelper.insertProperty(args.item, sortOrder); }, performItemRemove: (args) => { - console.log('perform remove'); return this._propertyStructureHelper.removeProperty(args.item.id!); }, }); @@ -92,6 +87,9 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle @state() protected _modalRouteNewProperty?: string; + @state() + _sortModeActive?: boolean; + constructor() { super(); @@ -99,10 +97,17 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle this._propertyStructureHelper.setStructureManager( (workspaceContext as UmbDocumentTypeWorkspaceContext).structure, ); + this.observe( + workspaceContext.isSorting, + (isSorting) => { + this._sortModeActive = isSorting; + this.#setModel(isSorting); + }, + '_observeIsSorting', + ); }); this.observe(this._propertyStructureHelper.propertyStructure, (propertyStructure) => { this._propertyStructure = propertyStructure; - this.#propertySorter.setModel(this._propertyStructure); }); // Note: Route for adding a new property @@ -125,6 +130,14 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle }); } + #setModel(isSorting?: boolean) { + if (isSorting) { + this.#propertySorter.setModel(this._propertyStructure); + } else { + this.#propertySorter.setModel([]); + } + } + connectedCallback(): void { super.connectedCallback(); const doctypes = this._propertyStructureHelper.ownerDocumentTypes; @@ -149,7 +162,7 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle return html`
${repeat( this._propertyStructure, - (property) => property.id ?? '' + property.containerId ?? '' + (property as any).sortOrder ?? '', + (property) => property.id ?? '' + property.containerId ?? '' + property.sortOrder ?? '', (property) => { // Note: This piece might be moved into the property component const inheritedFromDocument = this._ownerDocumentTypes?.find( @@ -157,11 +170,11 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle ); return html` { this._propertyStructureHelper.partialUpdateProperty(property.id, event.detail); @@ -173,13 +186,15 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle }, )}
- - Add property - `; + ${!this._sortModeActive + ? html` + Add property + ` + : ''} `; } static styles = [ @@ -188,20 +203,6 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle #add { width: 100%; } - document-type-workspace-view-edit-property { - position: relative; - } - - .select { - visibility: hidden; - } - - .select { - position: absolute; - inset: 0; - content: ''; - border: 2px solid red; - } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-property.element.ts index da4a7d82ae..1ab110139f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-property.element.ts @@ -50,6 +50,9 @@ export class UmbDocumentTypeWorkspacePropertyElement extends UmbLitElement { @property({ type: Boolean }) public inherited?: boolean; + @property({ type: Boolean, reflect: true, attribute: 'sort-mode-active' }) + public sortModeActive = false; + #dataTypeRepository = new UmbDataTypeRepository(this); #modalRegistration; @@ -82,7 +85,6 @@ export class UmbDocumentTypeWorkspacePropertyElement extends UmbLitElement { constructor() { super(); - this.#modalRegistration = new UmbModalRouteRegistrationController(this, UMB_PROPERTY_SETTINGS_MODAL) .addUniquePaths(['propertyId']) .onSetup(() => { @@ -181,68 +183,91 @@ export class UmbDocumentTypeWorkspacePropertyElement extends UmbLitElement { } } + renderSortableProperty() { + if (!this.property) return; + return html` +
+ + ${this.property.name} (${this.property.alias}) +
+ + `; + } + renderEditableProperty() { - return this.property - ? html` - - - ${this.renderPropertyTags()} - - - - - - - ` - : ''; + if (!this.property) return; + + if (this.sortModeActive) { + return this.renderSortableProperty(); + } else { + return html` + + + ${this.renderPropertyTags()} + + + + + + + `; + } } renderInheritedProperty() { - return this.property - ? html` - -
- ${this.renderPropertyTags()} - - - ${this.localize.term('contentTypeEditor_inheritedFrom')} - - ${this.ownerDocumentTypeName ?? '??'} - - - -
- ` - : ''; + if (!this.property) return; + + if (this.sortModeActive) { + return this.renderSortableProperty(); + } else { + return html` + +
+ ${this.renderPropertyTags()} + + + ${this.localize.term('contentTypeEditor_inheritedFrom')} + + ${this.ownerDocumentTypeName ?? '??'} + + + +
+ `; + } } renderPropertyAlias() { @@ -292,7 +317,7 @@ export class UmbDocumentTypeWorkspacePropertyElement extends UmbLitElement { static styles = [ UmbTextStyles, css` - :host { + :host(:not([sort-mode-active])) { display: grid; grid-template-columns: 200px auto; column-gap: var(--uui-size-layout-2); @@ -311,32 +336,52 @@ export class UmbDocumentTypeWorkspacePropertyElement extends UmbLitElement { } } + :host(:first-of-type) { + padding-top: 0; + } :host(:last-of-type) { border-bottom: none; } - :host(:first-of-type) { - padding-top: 0; + :host([sort-mode-active]) { + position: relative; + display: flex; + padding: 0; + margin-bottom: var(--uui-size-3); } - :host([draggable='true']) { + + :host([sort-mode-active]:last-of-type) { + margin-bottom: 0; + } + + :host([sort-mode-active]:not([inherited])) { cursor: grab; } + :host([sort-mode-active]) .sortable { + flex: 1; + display: flex; + background-color: var(--uui-color-divider); + align-items: center; + padding: 0 var(--uui-size-3); + gap: var(--uui-size-3); + } + + :host([sort-mode-active]) uui-input { + max-width: 75px; + } + /* Placeholder style, used when property is being dragged.*/ - :host(.--umb-sorter-placeholder) { - height: 2px; - } - :host(.--umb-sorter-placeholder) > div, - :host(.--umb-sorter-placeholder) > uui-button { - display: none; + :host(.--umb-sorter-placeholder) > * { + visibility: hidden; } + :host(.--umb-sorter-placeholder)::after { content: ''; - grid-column: span 2; - width: 100%; - border-top: 2px solid blue; - border-radius: 1px; - /* TODO: Make use of same highlight color as UUI and the same Animation. Consider making this a component/(available style) in UUI? */ + inset: 0; + position: absolute; + border: 1px dashed var(--uui-color-divider-emphasis); + border-radius: var(--uui-border-radius); } p { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-tab.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-tab.element.ts index 93fabafc3f..57d995f690 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-tab.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit-tab.element.ts @@ -5,11 +5,52 @@ import { UmbContentTypeContainerStructureHelper } from '@umbraco-cms/backoffice/ import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { PropertyTypeContainerModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace'; +import { UmbSorterConfig, UmbSorterController } from '@umbraco-cms/backoffice/sorter'; import './document-type-workspace-view-edit-properties.element.js'; +const SORTER_CONFIG: UmbSorterConfig = { + compareElementToModel: (element: HTMLElement, model: PropertyTypeContainerModelBaseModel) => { + return element.getAttribute('data-umb-group-id') === model.id; + }, + querySelectModelToElement: (container: HTMLElement, modelEntry: PropertyTypeContainerModelBaseModel) => { + return container.querySelector('data-umb-group-id=[' + modelEntry.id + ']'); + }, + identifier: 'content-type-group-sorter', + itemSelector: '[data-umb-group-id]', + disabledItemSelector: '[inherited]', + containerSelector: '#group-list', +}; + @customElement('umb-document-type-workspace-view-edit-tab') export class UmbDocumentTypeWorkspaceViewEditTabElement extends UmbLitElement { + public sorter?: UmbSorterController; + + config: UmbSorterConfig = { + ...SORTER_CONFIG, + performItemInsert: async (args) => { + if (!this._groups) return false; + const oldIndex = this._groups.findIndex((group) => group.id! === args.item.id); + if (args.newIndex === oldIndex) return true; + + let sortOrder = 0; + //TODO the sortOrder set is not correct + if (this._groups.length > 0) { + if (args.newIndex === 0) { + sortOrder = (this._groups[0].sortOrder ?? 0) - 1; + } else { + sortOrder = (this._groups[Math.min(args.newIndex, this._groups.length - 1)].sortOrder ?? 0) + 1; + } + + if (sortOrder !== args.item.sortOrder) { + await this._groupStructureHelper.partialUpdateContainer(args.item.id!, { sortOrder }); + } + } + + return true; + }, + }; + private _ownerTabId?: string | null; // TODO: get rid of this: @@ -65,9 +106,22 @@ export class UmbDocumentTypeWorkspaceViewEditTabElement extends UmbLitElement { constructor() { super(); + this.sorter = new UmbSorterController(this, this.config); + this.consumeContext(UMB_WORKSPACE_CONTEXT, (context) => { this._groupStructureHelper.setStructureManager((context as UmbDocumentTypeWorkspaceContext).structure); - this.observe(context.isSorting, (isSorting) => (this._sortModeActive = isSorting)); + this.observe( + context.isSorting, + (isSorting) => { + this._sortModeActive = isSorting; + if (isSorting) { + this.sorter?.setModel(this._groups); + } else { + this.sorter?.setModel([]); + } + }, + '_observeIsSorting', + ); }); this.observe(this._groupStructureHelper.containers, (groups) => { this._groups = groups; @@ -96,42 +150,62 @@ export class UmbDocumentTypeWorkspaceViewEditTabElement extends UmbLitElement { ` : ''} - ${repeat( - this._groups, - (group) => group.id ?? '' + group.name, - (group) => html` +
+ ${repeat( + this._groups, + (group) => group.id ?? '' + group.name, + (group) => html` ${ this._groupStructureHelper.isOwnerChildContainer(group.id!) ? html`
- { - const newName = (e.target as HTMLInputElement).value; - this._groupStructureHelper.updateContainerName(group.id!, group.parentId ?? null, newName); - }}> - +
+ ${this._sortModeActive ? html`` : ''} + + { + const newName = (e.target as HTMLInputElement).value; + this._groupStructureHelper.updateContainerName(group.id!, group.parentId ?? null, newName); + }}> + +
+ ${this._sortModeActive + ? html`` + : ''}
` - : html`
${group.name ?? ''} (Inherited)
` + : html`
+
${group.name ?? ''} (Inherited)
+ ${!this._sortModeActive + ? html`` + : ''} +
` }
- `, - )} - - ${this.localize.term('contentTypeEditor_addGroup')} - + `, + )} + + ${!this._sortModeActive + ? html` + ${this.localize.term('contentTypeEditor_addGroup')} + ` + : ''} `; } @@ -142,12 +216,48 @@ export class UmbDocumentTypeWorkspaceViewEditTabElement extends UmbLitElement { width: 100%; } - #add:not(:first-child) { - width: 100%; + #add:first-child { margin-top: var(--uui-size-layout-1); } - uui-box:not(:first-child) { - margin-top: var(--uui-size-layout-1); + uui-box { + margin-bottom: var(--uui-size-layout-1); + } + + [data-umb-group-id] { + display: block; + position: relative; + } + + div[slot='header'] { + display: flex; + align-items: center; + justify-content: space-between; + } + + div[slot='header'] > div { + display: flex; + align-items: center; + gap: var(--uui-size-3); + } + + uui-input[type='number'] { + max-width: 75px; + } + + .sorting { + cursor: grab; + } + + .--umb-sorter-placeholder > uui-box { + visibility: hidden; + } + + .--umb-sorter-placeholder::after { + content: ''; + inset: 0; + position: absolute; + border-radius: var(--uui-border-radius); + border: 1px dashed var(--uui-color-divider-emphasis); } `, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts index 96b3107510..641e7f909e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/views/design/document-type-workspace-view-edit.element.ts @@ -62,9 +62,6 @@ export class UmbDocumentTypeWorkspaceViewEditElement return true; }, - performItemRemove: () => { - return true; - }, }; //private _hasRootProperties = false; @@ -96,6 +93,7 @@ export class UmbDocumentTypeWorkspaceViewEditElement constructor() { super(); + this.sorter = new UmbSorterController(this, this.config); //TODO: We need to differentiate between local and composition tabs (and hybrids) @@ -111,6 +109,11 @@ export class UmbDocumentTypeWorkspaceViewEditElement this.consumeContext(UMB_WORKSPACE_CONTEXT, (workspaceContext) => { this._workspaceContext = workspaceContext as UmbDocumentTypeWorkspaceContext; this._tabsStructureHelper.setStructureManager((workspaceContext as UmbDocumentTypeWorkspaceContext).structure); + this.observe( + this._workspaceContext.isSorting, + (isSorting) => (this.sortModeActive = isSorting), + '_observeIsSorting', + ); this._observeRootGroups(); }); @@ -130,16 +133,16 @@ export class UmbDocumentTypeWorkspaceViewEditElement }, '_observeGroups', ); - - this.observe(this._workspaceContext.isSorting, (isSorting) => (this.sortModeActive = isSorting)); } #changeMode() { this._workspaceContext?.setIsSorting(!this.sortModeActive); - if (!this._tabs) return; - this.sorter = new UmbSorterController(this, this.config); - this.sorter.setModel(this._tabs); + if (this.sortModeActive && this._tabs) { + this.sorter?.setModel(this._tabs); + } else { + this.sorter?.setModel([]); + } } private _createRoutes() { @@ -303,7 +306,6 @@ export class UmbDocumentTypeWorkspaceViewEditElement : this.localize.term('general_reorder'); return html`
- ${this.renderHiddenActions()} ${this.localize.term('contentTypeEditor_compositions')} @@ -315,16 +317,6 @@ export class UmbDocumentTypeWorkspaceViewEditElement
`; } - renderHiddenActions() { - //TODO: If currently dragging a container of type "group", show this button - if (this.sortModeActive && this._activePath == 'dummy text to force false for now') { - return html` - ${this.localize.term('contentTypeEditor_convertToTab')} - `; - } - return; - } - renderTabsNavigation() { if (!this._tabs) return; @@ -369,10 +361,9 @@ export class UmbDocumentTypeWorkspaceViewEditElement renderTabInner(tab: PropertyTypeContainerModelBaseModel, tabActive: boolean, tabInherited: boolean) { if (this.sortModeActive) { return html`
- - ${!this._tabsStructureHelper.isOwnerContainer(tab.id!) + ${tabInherited ? html`${tab.name!}` - : html`${tab.name!} + : html` ${tab.name!}