sort on tab and properties

This commit is contained in:
Lone Iversen
2023-09-29 16:05:55 +02:00
committed by Jacob Overgaard
parent 56b692d45a
commit 44ad430db1
4 changed files with 302 additions and 155 deletions

View File

@@ -20,7 +20,6 @@ const SORTER_CONFIG: UmbSorterConfig<DocumentTypePropertyTypeResponseModel> = {
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`<div id="property-list">
${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`<document-type-workspace-view-edit-property
class="property"
data-umb-property-id=${ifDefined(property.id)}
owner-document-type-id=${ifDefined(inheritedFromDocument?.id)}
owner-document-type-name=${ifDefined(inheritedFromDocument?.name)}
?inherited=${property.containerId !== this.containerId}
?sort-mode-active=${this._sortModeActive}
.property=${property}
@partial-property-update=${(event: CustomEvent) => {
this._propertyStructureHelper.partialUpdateProperty(property.id, event.detail);
@@ -173,13 +186,15 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle
},
)}
</div>
<uui-button
label=${this.localize.term('contentTypeEditor_addProperty')}
id="add"
look="placeholder"
href=${ifDefined(this._modalRouteNewProperty)}>
<umb-localize key="contentTypeEditor_addProperty">Add property</umb-localize>
</uui-button> `;
${!this._sortModeActive
? html`<uui-button
label=${this.localize.term('contentTypeEditor_addProperty')}
id="add"
look="placeholder"
href=${ifDefined(this._modalRouteNewProperty)}>
<umb-localize key="contentTypeEditor_addProperty">Add property</umb-localize>
</uui-button> `
: ''} `;
}
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;
}
`,
];
}

View File

@@ -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`
<div class="sortable">
<uui-icon name="${this.inherited ? 'umb:merge' : 'umb:navigation'}"></uui-icon>
${this.property.name} <span style="color: var(--uui-color-disabled-contrast)">(${this.property.alias})</span>
</div>
<uui-input
type="number"
?readonly=${this.inherited}
label="sort order"
.value=${this.property.sortOrder ?? 0}></uui-input>
`;
}
renderEditableProperty() {
return this.property
? html`
<div id="header">
<uui-input
name="label"
id="label-input"
placeholder=${this.localize.term('placeholders_label')}
label="label"
.value=${this.property.name}
@input=${this.#onNameChange}></uui-input>
${this.renderPropertyAlias()}
<slot name="property-action-menu"></slot>
<p>
<uui-textarea
label="description"
name="description"
id="description-input"
placeholder=${this.localize.term('placeholders_enterDescription')}
.value=${this.property.description}
@input=${(e: CustomEvent) => {
if (e.target) this._singleValueUpdate('description', (e.target as HTMLInputElement).value);
}}></uui-textarea>
</p>
</div>
<uui-button
id="editor"
label=${this.localize.term('contentTypeEditor_editorSettings')}
href=${ifDefined(this._modalRoute)}>
${this.renderPropertyTags()}
<uui-action-bar>
<uui-button label="${this.localize.term('actions_delete')}" @click="${this.#requestRemove}">
<uui-icon name="delete"></uui-icon>
</uui-button>
</uui-action-bar>
</uui-button>
`
: '';
if (!this.property) return;
if (this.sortModeActive) {
return this.renderSortableProperty();
} else {
return html`
<div id="header">
<uui-input
name="label"
id="label-input"
placeholder=${this.localize.term('placeholders_label')}
label="label"
.value=${this.property.name}
@input=${this.#onNameChange}></uui-input>
${this.renderPropertyAlias()}
<slot name="property-action-menu"></slot>
<p>
<uui-textarea
label="description"
name="description"
id="description-input"
placeholder=${this.localize.term('placeholders_enterDescription')}
.value=${this.property.description}
@input=${(e: CustomEvent) => {
if (e.target) this._singleValueUpdate('description', (e.target as HTMLInputElement).value);
}}></uui-textarea>
</p>
</div>
<uui-button
id="editor"
label=${this.localize.term('contentTypeEditor_editorSettings')}
href=${ifDefined(this._modalRoute)}>
${this.renderPropertyTags()}
<uui-action-bar>
<uui-button label="${this.localize.term('actions_delete')}" @click="${this.#requestRemove}">
<uui-icon name="delete"></uui-icon>
</uui-button>
</uui-action-bar>
</uui-button>
`;
}
}
renderInheritedProperty() {
return this.property
? html`
<div id="header">
<b>${this.property.name}</b>
<i>${this.property.alias}</i>
<p>${this.property.description}</p>
</div>
<div id="editor">
${this.renderPropertyTags()}
<uui-tag look="default" class="inherited">
<uui-icon name="umb:merge"></uui-icon>
<span
>${this.localize.term('contentTypeEditor_inheritedFrom')}
<a href=${this._editDocumentTypePath + 'edit/' + this.ownerDocumentTypeId}>
${this.ownerDocumentTypeName ?? '??'}
</a>
</span>
</uui-tag>
</div>
`
: '';
if (!this.property) return;
if (this.sortModeActive) {
return this.renderSortableProperty();
} else {
return html`
<div id="header">
<b>${this.property.name}</b>
<i>${this.property.alias}</i>
<p>${this.property.description}</p>
</div>
<div id="editor">
${this.renderPropertyTags()}
<uui-tag look="default" class="inherited">
<uui-icon name="umb:merge"></uui-icon>
<span
>${this.localize.term('contentTypeEditor_inheritedFrom')}
<a href=${this._editDocumentTypePath + 'edit/' + this.ownerDocumentTypeId}>
${this.ownerDocumentTypeName ?? '??'}
</a>
</span>
</uui-tag>
</div>
`;
}
}
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 {

View File

@@ -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<PropertyTypeContainerModelBaseModel> = {
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<PropertyTypeContainerModelBaseModel>;
config: UmbSorterConfig<PropertyTypeContainerModelBaseModel> = {
...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 {
</uui-box>
`
: ''}
${repeat(
this._groups,
(group) => group.id ?? '' + group.name,
(group) => html`
<div id="group-list">
${repeat(
this._groups,
(group) => group.id ?? '' + group.name,
(group) => html`<span data-umb-group-id=${ifDefined(group.id)}>
<uui-box>
${
this._groupStructureHelper.isOwnerChildContainer(group.id!)
? html`
<div slot="header">
<uui-input
label="Group name"
placeholder="Enter a group name"
value=${group.name ?? ''}
@change=${(e: InputEvent) => {
const newName = (e.target as HTMLInputElement).value;
this._groupStructureHelper.updateContainerName(group.id!, group.parentId ?? null, newName);
}}>
</uui-input>
<div>
${this._sortModeActive ? html`<uui-icon name="umb:navigation"></uui-icon>` : ''}
<uui-input
label="Group name"
placeholder="Enter a group name"
value=${group.name ?? ''}
@change=${(e: InputEvent) => {
const newName = (e.target as HTMLInputElement).value;
this._groupStructureHelper.updateContainerName(group.id!, group.parentId ?? null, newName);
}}>
</uui-input>
</div>
${this._sortModeActive
? html`<uui-input type="number" label="sort order" .value=${group.sortOrder ?? 0}></uui-input>`
: ''}
</div>
`
: html`<div slot="header"><b>${group.name ?? ''}</b> (Inherited)</div>`
: html`<div slot="header">
<div><uui-icon name="umb:merge"></uui-icon><b>${group.name ?? ''}</b> (Inherited)</div>
${!this._sortModeActive
? html`<uui-input
readonly
type="number"
label="sort order"
.value=${group.sortOrder ?? 0}></uui-input>`
: ''}
</div>`
}
</div>
<umb-document-type-workspace-view-edit-properties
container-id=${ifDefined(group.id)}
container-type="Group"
container-name=${group.name || ''}></umb-document-type-workspace-view-edit-properties>
</uui-box>`,
)}
<uui-button
label=${this.localize.term('contentTypeEditor_addGroup')}
id="add"
look="placeholder"
@click=${this.#onAddGroup}>
${this.localize.term('contentTypeEditor_addGroup')}
</uui-button>
</uui-box></span>`,
)}
</div>
${!this._sortModeActive
? html`<uui-button
label=${this.localize.term('contentTypeEditor_addGroup')}
id="add"
look="placeholder"
@click=${this.#onAddGroup}>
${this.localize.term('contentTypeEditor_addGroup')}
</uui-button>`
: ''}
`;
}
@@ -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);
}
`,
];

View File

@@ -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`<div class="tab-actions">
${this.renderHiddenActions()}
<uui-button look="outline" label=${this.localize.term('contentTypeEditor_compositions')} compact>
<uui-icon name="umb:merge"></uui-icon>
${this.localize.term('contentTypeEditor_compositions')}
@@ -315,16 +317,6 @@ export class UmbDocumentTypeWorkspaceViewEditElement
</div>`;
}
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`<uui-button look="placeholder" label=${this.localize.term('contentTypeEditor_convertToTab')}>
${this.localize.term('contentTypeEditor_convertToTab')}
</uui-button>`;
}
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`<div class="no-edit">
<uui-icon name="umb:navigation" class="drag-${tab.id}"> </uui-icon>
${!this._tabsStructureHelper.isOwnerContainer(tab.id!)
${tabInherited
? html`<uui-icon class="external" name="umb:merge"></uui-icon>${tab.name!}`
: html`${tab.name!}
: html`<uui-icon name="umb:navigation" class="drag-${tab.id}"> </uui-icon>${tab.name!}
<uui-input
label="sort order"
type="number"