Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Handle mandatory languages in publish'ish dialogs #2408

Merged
merged 10 commits into from
Oct 4, 2024
Merged
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"Uncategorized",
"uninitialize",
"unprovide",
"unpublishing",
"variantable"
],
"exportall.config.folderListener": [],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../../types.js';
import { isNotPublishedMandatory } from '../utils.js';
import type { UmbDocumentPublishModalData, UmbDocumentPublishModalValue } from './document-publish-modal.token.js';
import { css, customElement, html, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
Expand All @@ -17,6 +18,9 @@ export class UmbDocumentPublishModalElement extends UmbModalBaseElement<
@state()
_options: Array<UmbDocumentVariantOptionModel> = [];

@state()
_hasNotSelectedMandatory?: boolean;

override firstUpdated() {
this.#configureSelectionManager();
}
Expand All @@ -25,25 +29,40 @@ export class UmbDocumentPublishModalElement extends UmbModalBaseElement<
this.#selectionManager.setMultiple(true);
this.#selectionManager.setSelectable(true);

// Only display variants that are relevant to pick from, i.e. variants that are draft or published with pending changes:
// Only display variants that are relevant to pick from, i.e. variants that are draft, not-published-mandatory or published with pending changes:
this._options =
this.data?.options.filter(
(option) => option.variant && option.variant.state !== UmbDocumentVariantState.NOT_CREATED,
(option) => isNotPublishedMandatory(option) || option.variant?.state !== UmbDocumentVariantState.NOT_CREATED,
) ?? [];

let selected = this.value?.selection ?? [];

// Filter selection based on options:
selected = selected.filter((s) => this._options.some((o) => o.unique === s));

this.#selectionManager.setSelection(selected);

// Additionally select mandatory languages:
// [NL]: I think for now lets make it an active choice to select the languages. If you just made them, they would be selected. So it just to underline the act of actually selecting these languages.
/*
this._options.forEach((variant) => {
if (variant.language?.isMandatory) {
this.#selectionManager.select(variant.unique);
selected.push(variant.unique);
}
});
*/

this.#selectionManager.setSelection(selected);

this.observe(
this.#selectionManager.selection,
(selection: Array<string>) => {
if (!this._options && !selection) return;

//Getting not published mandatory options — the options that are mandatory and not currently published.
const missingMandatoryOptions = this._options.filter(isNotPublishedMandatory);
this._hasNotSelectedMandatory = missingMandatoryOptions.some((option) => !selection.includes(option.unique));
},
'observeSelection',
);
}

#submit() {
Expand All @@ -63,6 +82,7 @@ export class UmbDocumentPublishModalElement extends UmbModalBaseElement<
<umb-document-variant-language-picker
.selectionManager=${this.#selectionManager}
.variantLanguageOptions=${this._options}
.requiredFilter=${isNotPublishedMandatory}
.pickableFilter=${this.data?.pickableFilter}></umb-document-variant-language-picker>

<div slot="actions">
Expand All @@ -71,6 +91,7 @@ export class UmbDocumentPublishModalElement extends UmbModalBaseElement<
label="${this.localize.term('buttons_saveAndPublish')}"
look="primary"
color="positive"
?disabled=${this._hasNotSelectedMandatory}
@click=${this.#submit}></uui-button>
</div>
</umb-body-layout> `;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../../types.js';
import { isNotPublishedMandatory } from '../utils.js';
import type {
UmbDocumentPublishWithDescendantsModalData,
UmbDocumentPublishWithDescendantsModalValue,
Expand All @@ -21,6 +22,9 @@ export class UmbDocumentPublishWithDescendantsModalElement extends UmbModalBaseE
@state()
_options: Array<UmbDocumentVariantOptionModel> = [];

@state()
_hasNotSelectedMandatory?: boolean;

override firstUpdated() {
this.#configureSelectionManager();
}
Expand All @@ -29,25 +33,40 @@ export class UmbDocumentPublishWithDescendantsModalElement extends UmbModalBaseE
this.#selectionManager.setMultiple(true);
this.#selectionManager.setSelectable(true);

// Only display variants that are relevant to pick from, i.e. variants that are draft or published with pending changes:
// Only display variants that are relevant to pick from, i.e. variants that are draft, not-published-mandatory or published with pending changes:
this._options =
this.data?.options.filter(
(option) => option.variant && option.variant.state !== UmbDocumentVariantState.NOT_CREATED,
(option) => isNotPublishedMandatory(option) || option.variant?.state !== UmbDocumentVariantState.NOT_CREATED,
) ?? [];

let selected = this.value?.selection ?? [];

// Filter selection based on options:
selected = selected.filter((s) => this._options.some((o) => o.unique === s));

this.#selectionManager.setSelection(selected);

// Additionally select mandatory languages:
// [NL]: I think for now lets make it an active choice to select the languages. If you just made them, they would be selected. So it just to underline the act of actually selecting these languages.
/*
this._options.forEach((variant) => {
if (variant.language?.isMandatory) {
this.#selectionManager.select(variant.unique);
selected.push(variant.unique);
}
});
*/

this.#selectionManager.setSelection(selected);

this.observe(
this.#selectionManager.selection,
(selection: Array<string>) => {
if (!this._options && !selection) return;

//Getting not published mandatory options — the options that are mandatory and not currently published.
const missingMandatoryOptions = this._options.filter(isNotPublishedMandatory);
this._hasNotSelectedMandatory = missingMandatoryOptions.some((option) => !selection.includes(option.unique));
},
'observeSelection',
);
}

#submit() {
Expand Down Expand Up @@ -83,6 +102,7 @@ export class UmbDocumentPublishWithDescendantsModalElement extends UmbModalBaseE
<umb-document-variant-language-picker
.selectionManager=${this.#selectionManager}
.variantLanguageOptions=${this._options}
.requiredFilter=${isNotPublishedMandatory}
.pickableFilter=${this.data?.pickableFilter}></umb-document-variant-language-picker>

<uui-form-layout-item>
Expand All @@ -99,6 +119,7 @@ export class UmbDocumentPublishWithDescendantsModalElement extends UmbModalBaseE
label="${this.localize.term('buttons_publishDescendants')}"
look="primary"
color="positive"
?disabled=${this._hasNotSelectedMandatory}
@click=${this.#submit}></uui-button>
</div>
</umb-body-layout> `;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
}

// Only display variants that are relevant to pick from, i.e. variants that are draft or published with pending changes:
// TODO:[NL] I would say we should change this, the act of scheduling should be equivalent to save & publishing. Resulting in content begin saved as part of carrying out the action. (But this requires a update in the workspace.)
this._options =
this.data?.options.filter(
(option) => option.variant && option.variant.state !== UmbDocumentVariantState.NOT_CREATED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class UmbDocumentVariantLanguagePickerElement extends UmbLitElement {
this.#selectionManager = value;
this.observe(
this.selectionManager.selection,
async (selection) => {
(selection) => {
this._selection = selection;
},
'_selectionManager',
Expand All @@ -46,6 +46,14 @@ export class UmbDocumentVariantLanguagePickerElement extends UmbLitElement {
@property({ attribute: false })
public pickableFilter?: (item: UmbDocumentVariantOptionModel) => boolean;

/**
* A filter function that determines if an item should be highlighted as a must select.
* @memberof UmbDocumentVariantLanguagePickerElement
* @returns {boolean} - True if the item is pickableFilter, false otherwise.
*/
@property({ attribute: false })
public requiredFilter?: (item: UmbDocumentVariantOptionModel) => boolean;

protected override updated(_changedProperties: PropertyValues): void {
super.updated(_changedProperties);

Expand All @@ -71,29 +79,32 @@ export class UmbDocumentVariantLanguagePickerElement extends UmbLitElement {

#renderItem(option: UmbDocumentVariantOptionModel) {
const pickable = this.pickableFilter ? this.pickableFilter(option) : () => true;
const selected = this._selection.includes(option.unique);
const mustSelect = (!selected && this.requiredFilter?.(option)) ?? false;
return html`
<uui-menu-item
class=${mustSelect ? 'required' : ''}
?selectable=${pickable}
?disabled=${!pickable}
label=${option.variant?.name ?? option.language.name}
@selected=${() => this.selectionManager.select(option.unique)}
@deselected=${() => this.selectionManager.deselect(option.unique)}
?selected=${this._selection.includes(option.unique)}>
?selected=${selected}>
<uui-icon slot="icon" name="icon-globe"></uui-icon>
${UmbDocumentVariantLanguagePickerElement.renderLabel(option)}
${UmbDocumentVariantLanguagePickerElement.renderLabel(option, mustSelect)}
</uui-menu-item>
`;
}

static renderLabel(option: UmbDocumentVariantOptionModel) {
static renderLabel(option: UmbDocumentVariantOptionModel, mustSelect?: boolean) {
return html`<div class="label" slot="label">
<strong> ${option.language.name} </strong>
<div class="label-status">${UmbDocumentVariantLanguagePickerElement.renderVariantStatus(option)}</div>
${option.language.isMandatory && option.variant?.state !== UmbDocumentVariantState.PUBLISHED
${option.language.isMandatory && mustSelect
? html`<div class="label-status">
<umb-localize key="languages_mandatoryLanguage">Mandatory language</umb-localize>
</div>`
: ''}
: nothing}
</div>`;
}

Expand All @@ -106,17 +117,17 @@ export class UmbDocumentVariantLanguagePickerElement extends UmbLitElement {
case UmbDocumentVariantState.DRAFT:
return html`<umb-localize key="content_unpublished">Draft</umb-localize>`;
case UmbDocumentVariantState.NOT_CREATED:
return html`<umb-localize key="content_notCreated">Not created</umb-localize>`;
default:
return nothing;
return html`<umb-localize key="content_notCreated">Not created</umb-localize>`;
}
}

static override styles = [
UmbTextStyles,
css`
#subtitle {
margin-top: 0;
.required {
color: var(--uui-color-danger);
--uui-menu-item-color-hover: var(--uui-color-danger-emphasis);
}
.label {
padding: 0.5rem 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,48 +12,72 @@ import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils';

import '../shared/document-variant-language-picker.element.js';

/**
* @function isPublished
* @param {UmbDocumentVariantOptionModel} option - the option to check.
* @returns {boolean} boolean
*/
export function isPublished(option: UmbDocumentVariantOptionModel): boolean {
return (
option.variant?.state === UmbDocumentVariantState.PUBLISHED ||
option.variant?.state === UmbDocumentVariantState.PUBLISHED_PENDING_CHANGES
);
}

@customElement('umb-document-unpublish-modal')
export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
UmbDocumentUnpublishModalData,
UmbDocumentUnpublishModalValue
> {
#selectionManager = new UmbSelectionManager<string>(this);
protected readonly _selectionManager = new UmbSelectionManager<string>(this);
#referencesRepository = new UmbDocumentReferenceRepository(this);

@state()
_options: Array<UmbDocumentVariantOptionModel> = [];

@state()
_selection: Array<string> = [];

@state()
_hasReferences = false;

@state()
_hasUnpublishPermission = true;

@state()
_hasInvalidSelection = true;

override firstUpdated() {
this.#configureSelectionManager();
this.#getReferences();
}

async #configureSelectionManager() {
this.#selectionManager.setMultiple(true);
this.#selectionManager.setSelectable(true);
this._selectionManager.setMultiple(true);
this._selectionManager.setSelectable(true);

// Only display variants that are relevant to pick from, i.e. variants that are draft or published with pending changes:
this._options =
this.data?.options.filter(
(option) =>
option.variant &&
(!option.variant.state ||
option.variant.state === UmbDocumentVariantState.PUBLISHED ||
option.variant.state === UmbDocumentVariantState.PUBLISHED_PENDING_CHANGES),
) ?? [];
this._options = this.data?.options.filter((option) => isPublished(option)) ?? [];

let selected = this.value?.selection ?? [];

// Filter selection based on options:
selected = selected.filter((s) => this._options.some((o) => o.unique === s));

this.#selectionManager.setSelection(selected);
this._selectionManager.setSelection(selected);

this.observe(
this._selectionManager.selection,
(selection) => {
this._selection = selection;
const selectionHasMandatory = this._options.some((o) => o.language.isMandatory && selection.includes(o.unique));
const selectionDoesNotHaveAllMandatory = this._options.some(
(o) => o.language.isMandatory && !selection.includes(o.unique),
);
this._hasInvalidSelection = selectionHasMandatory && selectionDoesNotHaveAllMandatory;
},
'observeSelection',
);
}

async #getReferences() {
Expand All @@ -80,7 +104,7 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<

#submit() {
if (this._hasUnpublishPermission) {
this.value = { selection: this.#selectionManager.getSelection() };
this.value = { selection: this._selection };
this.modalContext?.submit();
return;
}
Expand All @@ -91,6 +115,10 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
this.modalContext?.reject();
}

private _requiredFilter = (variantOption: UmbDocumentVariantOptionModel): boolean => {
return variantOption.language.isMandatory && !this._selection.includes(variantOption.unique);
};

override render() {
return html`<umb-body-layout headline=${this.localize.term('content_unpublish')}>
<p id="subtitle">
Expand All @@ -100,8 +128,9 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
</p>
<umb-document-variant-language-picker
.selectionManager=${this.#selectionManager}
.selectionManager=${this._selectionManager}
.variantLanguageOptions=${this._options}
.requiredFilter=${this._hasInvalidSelection ? this._requiredFilter : undefined}
.pickableFilter=${this.data?.pickableFilter}></umb-document-variant-language-picker>
<p>
Expand Down Expand Up @@ -130,7 +159,7 @@ export class UmbDocumentUnpublishModalElement extends UmbModalBaseElement<
<uui-button label=${this.localize.term('general_close')} @click=${this.#close}></uui-button>
<uui-button
label="${this.localize.term('actions_unpublish')}"
?disabled=${!this._hasUnpublishPermission || !this.#selectionManager.getSelection().length}
?disabled=${this._hasInvalidSelection || !this._hasUnpublishPermission || this._selection.length === 0}
look="primary"
color="warning"
@click=${this.#submit}></uui-button>
Expand Down
14 changes: 14 additions & 0 deletions src/packages/documents/documents/modals/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../types.js';

/**
* @function isNotPublishedMandatory
* @param {UmbDocumentVariantOptionModel} option - the option to check.
* @returns {boolean} boolean
*/
export function isNotPublishedMandatory(option: UmbDocumentVariantOptionModel): boolean {
return (
option.language.isMandatory &&
option.variant?.state !== UmbDocumentVariantState.PUBLISHED &&
option.variant?.state !== UmbDocumentVariantState.PUBLISHED_PENDING_CHANGES
);
}
Loading
Loading