Merge pull request #2410 from umbraco/v15/feature/select-all-option-in-publishing-dialogs

Feature: Select all variants option in publishing dialogs
This commit is contained in:
Niels Lyngsø
2024-10-11 16:30:06 +02:00
committed by GitHub
30 changed files with 146 additions and 18 deletions

View File

@@ -318,6 +318,7 @@ export default {
createEmpty: 'Napravi novi',
createFromClipboard: 'Zalijepi iz međuspremnika',
nodeIsInTrash: 'Ova stavka je u korpi za otpatke',
saveModalTitle: 'Spremi',
},
blueprints: {
createBlueprintFrom: 'Kreirajte novi predložak sadržaja iz <em>%0%</em>',

View File

@@ -300,6 +300,7 @@ export default {
createEmpty: 'Create new',
createFromClipboard: 'Paste from clipboard',
nodeIsInTrash: 'This item is in the Recycle Bin',
saveModalTitle: 'Uložit',
},
blueprints: {
createBlueprintFrom: 'Vytvořit novou šablonu obsahu z <em>%0%</em>',

View File

@@ -331,6 +331,7 @@ export default {
variantSendForApprovalNotAllowed: 'Ni chaniateir anfon am gymeradwyaeth',
variantScheduleNotAllowed: 'Ni chaniateir amserlennu',
variantUnpublishNotAllowed: 'Ni chaniateir dad-gyhoeddi',
saveModalTitle: 'Achub',
},
blueprints: {
createBlueprintFrom: "Creu Templed Cynnwys newydd o '%0%'",

View File

@@ -326,6 +326,7 @@ export default {
variantSendForApprovalNotAllowed: 'Send for approval is not allowed',
variantScheduleNotAllowed: 'Schedule is not allowed',
variantUnpublishNotAllowed: 'Unpublish is not allowed',
saveModalTitle: 'Gem',
},
blueprints: {
createBlueprintFrom: "Opret en ny indholdsskabelon fra '%0%'",

View File

@@ -332,6 +332,7 @@ export default {
variantSendForApprovalNotAllowed: 'Zur Genehmigung senden ist nicht erlaubt.',
variantScheduleNotAllowed: 'Plannung ist nicht erlaubt',
variantUnpublishNotAllowed: 'Veröffentlichung zurücknehmen ist nicht erlaubt.',
saveModalTitle: 'Speichern',
},
blueprints: {
createBlueprintFrom: 'Erzeuge eine neue Inhaltsvorlage von <em>%0%</em>',

View File

@@ -345,6 +345,7 @@ export default {
variantScheduleNotAllowed: 'Schedule is not allowed',
variantUnpublishNotAllowed: 'Unpublish is not allowed',
selectAllVariants: 'Select all variants',
saveModalTitle: 'Save',
},
blueprints: {
createBlueprintFrom: "Create a new Document Blueprint from '%0%'",
@@ -930,6 +931,7 @@ export default {
header: 'Header',
systemField: 'system field',
lastUpdated: 'Last Updated',
selectAll: 'Select all',
skipToMenu: 'Skip to menu',
skipToContent: 'Skip to content',
restore: 'Restore',

View File

@@ -341,6 +341,7 @@ export default {
variantScheduleNotAllowed: 'Schedule is not allowed',
variantUnpublishNotAllowed: 'Unpublish is not allowed',
selectAllVariants: 'Select all variants',
saveModalTitle: 'Save',
},
blueprints: {
createBlueprintFrom: "Create a new Document Blueprint from '%0%'",
@@ -953,6 +954,7 @@ export default {
addChild: 'Add child',
editDataType: 'Edit data type',
navigateSections: 'Navigate sections',
selectAll: 'Select all',
shortcut: 'Shortcuts',
showShortcuts: 'show shortcuts',
toggleListView: 'Toggle list view',

View File

@@ -202,6 +202,7 @@ export default {
addTextBox: 'Añadir otra caja de texto',
removeTextBox: 'Eliminar caja de texto',
contentRoot: 'Raíz de contenido',
saveModalTitle: 'Guardar',
},
blueprints: {
createBlueprintFrom: 'Crear nueva Plantilla de Contenido desde <em>%0%</em>',

View File

@@ -297,6 +297,7 @@ export default {
schedulePublishHelp: "Sélectionnez la date et l'heure de publication/dépublication de l'élément de contenu.",
createEmpty: 'Créer nouveau',
createFromClipboard: 'Copier du clipboard',
saveModalTitle: 'Sauver',
},
blueprints: {
createBlueprintFrom: 'Créer un nouveau Modèle de Contenu à partir de <em>%0%</em>',

View File

@@ -113,6 +113,7 @@ export default {
updateDate: 'נערך לאחרונה',
uploadClear: 'הסר קובץ',
urls: 'קשר למסמך',
saveModalTitle: 'שמור',
},
create: {
chooseNode: 'היכן ברצונך ליצור את %0%',

View File

@@ -319,6 +319,7 @@ export default {
createEmpty: 'Kreiraj novo',
createFromClipboard: 'Zalijepi iz međuspremnika',
nodeIsInTrash: 'Ova stavka je u košu za smeće',
saveModalTitle: 'Spremi',
},
blueprints: {
createBlueprintFrom: 'Kreirajte novi predložak sadržaja iz <em>%0%</em>',

View File

@@ -331,6 +331,7 @@ export default {
createEmpty: 'Crea nuovo/a',
createFromClipboard: 'Incolla dagli appunti',
nodeIsInTrash: 'Questo articolo è nel cestino',
saveModalTitle: 'Salva',
},
blueprints: {
createBlueprintFrom: "Crea un nuovo modello di contenuto da '%0%'",

View File

@@ -148,6 +148,7 @@ export default {
notmemberof: 'グループのメンバーではありません',
childItems: '子コンテンツ',
target: 'ターゲット',
saveModalTitle: '保存',
},
media: {
clickToUpload: 'クリックしてアップロードする',

View File

@@ -113,6 +113,7 @@ export default {
updateDate: '마지막 수정일',
uploadClear: '파일 삭제',
urls: '문서에 링크',
saveModalTitle: '저장',
},
create: {
chooseNode: '새로운 %0% (을)를 생성할 위치를 지정하십시오',

View File

@@ -146,6 +146,7 @@ export default {
notmemberof: 'Ikke medlem av gruppe(ne)',
childItems: 'Undersider',
target: 'Åpne i vindu',
saveModalTitle: 'Lagre',
},
media: {
clickToUpload: 'Klikk for å laste opp',

View File

@@ -317,6 +317,7 @@ export default {
createEmpty: 'Maak nieuw',
createFromClipboard: 'Plakken vanaf het klembord',
nodeIsInTrash: 'Dit item is in de prullenbak',
saveModalTitle: 'Opslaan',
},
blueprints: {
createBlueprintFrom: 'Nieuw Inhoudssjabloon aanmaken voor <em>%0%</em>',

View File

@@ -199,6 +199,7 @@ export default {
addTextBox: 'Dodaj kolejne pole tekstowe',
removeTextBox: 'Usuń te pole tekstowe',
contentRoot: 'Korzeń zawartości',
saveModalTitle: 'Zapisz',
},
blueprints: {
createBlueprintFrom: 'Stwórz nowy Szablon Zawartości z <em>%0%</em>',

View File

@@ -113,6 +113,7 @@ export default {
updateDate: 'Última edição',
uploadClear: 'Remover arquivo',
urls: 'Link ao documento',
saveModalTitle: 'Salvar',
},
create: {
chooseNode: 'Onde você quer criar seu novo(a) %0%',

View File

@@ -250,6 +250,7 @@ export default {
urls: 'Ссылка на документ',
addTextBox: 'Добавить новое поле текста',
removeTextBox: 'Удалить это поле текста',
saveModalTitle: 'Сохранить',
},
contentPicker: {
pickedTrashedItem: 'Выбран элемент содержимого, который в настоящее время удален или находится в корзине',

View File

@@ -233,6 +233,7 @@ export default {
listViewNoContent: 'Inga undernoder har lagts till',
noChanges: 'Inga ändringar har gjorts',
notCreated: 'Ej skapad',
saveModalTitle: 'Spara',
},
contentTypeEditor: {
yesDelete: 'Ja, ta bort',

View File

@@ -305,6 +305,7 @@ export default {
createEmpty: 'Yeni oluştur',
createFromClipboard: 'Panodan yapıştır',
nodeIsInTrash: "Bu öğe Geri Dönüşüm Kutusu'nda",
saveModalTitle: 'Kaydet',
},
blueprints: {
createBlueprintFrom: '<em>%0%</em> den yeni bir İçerik Şablonu oluşturun',

View File

@@ -250,6 +250,7 @@ export default {
urls: 'Посилання на документ',
addTextBox: 'Додати нове текстове поле',
removeTextBox: 'Видалити це текстове поле',
saveModalTitle: 'Зберегти',
},
contentPicker: {
pickedTrashedItem: 'Вибрано елемент вмісту, який вилучено або знаходиться в корзині.',

View File

@@ -154,6 +154,7 @@ export default {
scheduledPublishServerTime: '这将转换到服务器上的以下时间:',
scheduledPublishDocumentation:
'<a href="https://docs.umbraco.com/umbraco-cms/fundamentals/data/scheduled-publishing#timezones" target="_blank" rel="noopener">这是什么意思?</a>',
saveModalTitle: '保存',
},
media: {
clickToUpload: '点击上传',

View File

@@ -153,6 +153,7 @@ export default {
scheduledPublishServerTime: '預計發表的時間(伺服器端)',
scheduledPublishDocumentation:
'<a href="https://docs.umbraco.com/umbraco-cms/fundamentals/data/scheduled-publishing#timezones" target="_blank" rel="noopener">這是什麼意思?</a>',
saveModalTitle: '保存',
},
media: {
clickToUpload: '點選以便上傳',

View File

@@ -238,6 +238,12 @@ describe('UmbSelectionManager', () => {
expect(manager.getSelection()).to.deep.equal(['1', '2']);
});
it('cant do the selection if the selection contains an item that is not allowed', () => {
manager.setAllowLimitation((item) => item !== '2');
expect(() => manager.setSelection(['1', '2'])).to.throw();
expect(manager.getSelection()).to.deep.equal([]);
});
it('deselects multiple items', () => {
manager.setSelection(['1', '2', '3']);
manager.deselect('1');

View File

@@ -60,6 +60,13 @@ export class UmbSelectionManager<ValueType extends string | null = string | null
public setSelection(value: Array<ValueType>) {
if (this.getSelectable() === false) return;
if (value === undefined) throw new Error('Value cannot be undefined');
value.forEach((unique) => {
if (this.#allow(unique) === false) {
throw new Error(`${unique} is now allowed to be selected`);
}
});
const newSelection = this.getMultiple() ? value : value.slice(0, 1);
this.#selection.setValue(newSelection);
}
@@ -161,4 +168,13 @@ export class UmbSelectionManager<ValueType extends string | null = string | null
public setAllowLimitation(compareFn: (unique: ValueType) => boolean): void {
this.#allow = compareFn;
}
/**
* Returns the function that determines if an item is selectable or not.
* @returns {*}
* @memberof UmbSelectionManager
*/
public getAllowLimitation() {
return this.#allow;
}
}

View File

@@ -46,7 +46,7 @@ export class UmbDocumentSaveModalElement extends UmbModalBaseElement<
}
override render() {
return html`<umb-body-layout headline=${this.localize.term('content_readyToSave')}>
return html`<umb-body-layout headline=${this.localize.term('content_saveModalTitle')}>
<p id="subtitle">
<umb-localize key="content_variantsToSave">Choose which variants to be saved.</umb-localize>
</p>

View File

@@ -6,6 +6,7 @@ import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils';
import type { UmbInputDateElement } from '@umbraco-cms/backoffice/components';
import type { UUIBooleanInputElement } from '@umbraco-cms/backoffice/external/uui';
@customElement('umb-document-schedule-modal')
export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
@@ -20,6 +21,9 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
@state()
_selection: UmbDocumentScheduleModalValue['selection'] = [];
@state()
_isAllSelected?: boolean;
constructor() {
super();
this.observe(
@@ -28,6 +32,7 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
this._selection = selection.map((unique) => {
return { unique, schedule: {} };
});
this._isAllSelected = this.#isAllSelected();
},
'_selection',
);
@@ -78,6 +83,25 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
return this._selection.some((s) => s.unique === unique);
}
#onSelectAllChange(event: Event) {
const allUniques = this._options.map((o) => o.unique);
const filter = this.#selectionManager.getAllowLimitation();
const allowedUniques = allUniques.filter((unique) => filter(unique));
if ((event.target as UUIBooleanInputElement).checked) {
this.#selectionManager.setSelection(allowedUniques);
} else {
this.#selectionManager.setSelection([]);
}
}
#isAllSelected() {
const allUniques = this._options.map((o) => o.unique);
const filter = this.#selectionManager.getAllowLimitation();
const allowedUniques = allUniques.filter((unique) => filter(unique));
return this._selection.length === allowedUniques.length;
}
override render() {
return html`<umb-body-layout headline=${this.localize.term('general_scheduledPublishing')}>
<p id="subtitle">
@@ -108,11 +132,18 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
}
#renderOptions() {
return repeat(
this._options,
(option) => option.unique,
(option) => this.#renderItem(option),
);
return html`
<uui-checkbox
@change=${this.#onSelectAllChange}
label=${this.localize.term('general_selectAll')}
.checked=${this._isAllSelected}></uui-checkbox>
${repeat(
this._options,
(option) => option.unique,
(option) => this.#renderItem(option),
)}
`;
}
#renderItem(option: UmbDocumentVariantOptionModel) {
@@ -210,6 +241,14 @@ export class UmbDocumentScheduleModalElement extends UmbModalBaseElement<
.publish-date > uui-form-layout-item:first-child {
border-right: 1px dashed var(--uui-color-border);
}
uui-checkbox {
margin-bottom: var(--uui-size-space-3);
}
uui-menu-item {
--uui-menu-item-flat-structure: 1;
}
`,
];
}

View File

@@ -1,4 +1,5 @@
import { UmbDocumentVariantState, type UmbDocumentVariantOptionModel } from '../../types.js';
import type { UUIBooleanInputElement } from '@umbraco-cms/backoffice/external/uui';
import {
css,
customElement,
@@ -27,6 +28,7 @@ export class UmbDocumentVariantLanguagePickerElement extends UmbLitElement {
this.selectionManager.selection,
(selection) => {
this._selection = selection;
this._isAllSelected = this.#isAllSelected();
},
'_selectionManager',
);
@@ -38,6 +40,9 @@ export class UmbDocumentVariantLanguagePickerElement extends UmbLitElement {
@state()
_selection: Array<string> = [];
@state()
_isAllSelected?: boolean;
/**
* A filter function that determines if an item is pickableFilter or not.
* @memberof UmbDocumentVariantLanguagePickerElement
@@ -65,16 +70,43 @@ export class UmbDocumentVariantLanguagePickerElement extends UmbLitElement {
}
}
#onSelectAllChange(event: Event) {
const allUniques = this.variantLanguageOptions.map((o) => o.unique);
const filter = this.selectionManager.getAllowLimitation();
const allowedUniques = allUniques.filter((unique) => filter(unique));
if ((event.target as UUIBooleanInputElement).checked) {
this.selectionManager.setSelection(allowedUniques);
} else {
this.selectionManager.setSelection([]);
}
}
#isAllSelected() {
const allUniques = this.variantLanguageOptions.map((o) => o.unique);
const filter = this.selectionManager.getAllowLimitation();
const allowedUniques = allUniques.filter((unique) => filter(unique));
return this._selection.length === allowedUniques.length;
}
override render() {
return this.variantLanguageOptions.length
? repeat(
this.variantLanguageOptions,
(option) => option.unique,
(option) => html` ${this.#renderItem(option)} `,
)
: html`<uui-box>
<umb-localize key="content_noVariantsToProcess">There are no available variants</umb-localize>
</uui-box>`;
if (this.variantLanguageOptions.length === 0) {
return html`<uui-box>
<umb-localize key="content_noVariantsToProcess">There are no available variants</umb-localize>
</uui-box>`;
}
return html`
<uui-checkbox
@change=${this.#onSelectAllChange}
label=${this.localize.term('general_selectAll')}
.checked=${this._isAllSelected}></uui-checkbox>
${repeat(
this.variantLanguageOptions,
(option) => option.unique,
(option) => html` ${this.#renderItem(option)} `,
)}
`;
}
#renderItem(option: UmbDocumentVariantOptionModel) {
@@ -135,6 +167,14 @@ export class UmbDocumentVariantLanguagePickerElement extends UmbLitElement {
.label-status {
font-size: 0.8rem;
}
uui-menu-item {
--uui-menu-item-flat-structure: 1;
}
uui-checkbox {
margin-bottom: var(--uui-size-space-3);
}
`,
];
}

View File

@@ -76,9 +76,10 @@ export class UmbLogViewerLogLevelFilterMenuElement extends UmbLitElement {
<umb-log-viewer-level-tag .level=${logLevel}></umb-log-viewer-level-tag>
</uui-checkbox>`,
)}
<uui-button class="log-level-menu-item" @click=${this.#selectAllLogLevels} label="Select all"
>Select all</uui-button
>
<uui-button
class="log-level-menu-item"
@click=${this.#selectAllLogLevels}
label=${this.localize.term('general_selectAll')}></uui-button>
<uui-button class="log-level-menu-item" @click=${this.#deselectAllLogLevels} label="Deselect all"
>Deselect all</uui-button
>