Merge remote-tracking branch 'origin/main' into feature/tinymce-servervariables

This commit is contained in:
Jacob Overgaard
2024-01-09 13:43:37 +01:00
15 changed files with 442 additions and 10 deletions

View File

@@ -0,0 +1,18 @@
import { UmbModalToken, UmbPickerModalValue, UmbTreePickerModalData } from '@umbraco-cms/backoffice/modal';
import { UmbEntityTreeItemModel } from '@umbraco-cms/backoffice/tree';
export type UmbMemberTypePickerModalData = UmbTreePickerModalData<UmbEntityTreeItemModel>;
export type UmbMemberTypePickerModalValue = UmbPickerModalValue;
export const UMB_MEMBER_TYPE_PICKER_MODAL = new UmbModalToken<UmbMemberTypePickerModalData, UmbMemberTypePickerModalValue>(
'Umb.Modal.TreePicker',
{
modal: {
type: 'sidebar',
size: 'small',
},
data: {
treeAlias: 'Umb.Tree.MemberType',
},
},
);

View File

@@ -4,6 +4,8 @@ import { manifests as memberGroupManifests } from './member-groups/manifests.js'
import { manifests as memberTypeManifests } from './member-types/manifests.js';
import { manifests as memberManifests } from './members/manifests.js';
import './members/components/index.js';
export const manifests = [
...memberSectionManifests,
...menuSectionManifests,

View File

@@ -0,0 +1 @@
import './input-member-type/input-member-type.element.js';

View File

@@ -0,0 +1,13 @@
import { UMB_MEMBER_TYPE_PICKER_MODAL } from '../../../../core/modal/token/member-type-picker-modal.token.js';
import { UMB_MEMBER_TYPE_REPOSITORY_ALIAS } from '../../repository/index.js';
import { UmbPickerInputContext } from '@umbraco-cms/backoffice/picker-input';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { MemberTypeItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
export class UmbMemberTypePickerContext extends UmbPickerInputContext<MemberTypeItemResponseModel> {
constructor(host: UmbControllerHostElement) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
super(host, UMB_MEMBER_TYPE_REPOSITORY_ALIAS, UMB_MEMBER_TYPE_PICKER_MODAL);
}
}

View File

@@ -0,0 +1,179 @@
import { UmbMemberTypePickerContext } from './input-member-type.context.js';
import { css, html, customElement, property, state, ifDefined, repeat } from '@umbraco-cms/backoffice/external/lit';
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import type { MemberTypeItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
@customElement('umb-input-member-type')
export class UmbMemberTypeInputElement extends FormControlMixin(UmbLitElement) {
/**
* This is a minimum amount of selected items in this input.
* @type {number}
* @attr
* @default 0
*/
@property({ type: Number })
public get min(): number {
return this.#pickerContext.min;
}
public set min(value: number) {
this.#pickerContext.min = value;
}
/**
* Min validation message.
* @type {boolean}
* @attr
* @default
*/
@property({ type: String, attribute: 'min-message' })
minMessage = 'This field need more items';
/**
* This is a maximum amount of selected items in this input.
* @type {number}
* @attr
* @default Infinity
*/
@property({ type: Number })
public get max(): number {
return this.#pickerContext.max;
}
public set max(value: number) {
this.#pickerContext.max = value;
}
/**
* Max validation message.
* @type {boolean}
* @attr
* @default
*/
@property({ type: String, attribute: 'min-message' })
maxMessage = 'This field exceeds the allowed amount of items';
public get selectedIds(): Array<string> {
return this.#pickerContext.getSelection();
}
public set selectedIds(ids: Array<string>) {
this.#pickerContext.setSelection(ids);
}
@property()
public set value(idsString: string) {
// Its with full purpose we don't call super.value, as thats being handled by the observation of the context selection.
this.selectedIds = splitStringToArray(idsString);
}
@property()
get pickableFilter() {
return this.#pickerContext.pickableFilter;
}
set pickableFilter(newVal) {
this.#pickerContext.pickableFilter = newVal;
}
@state()
private _items?: Array<MemberTypeItemResponseModel>;
#pickerContext = new UmbMemberTypePickerContext(this);
constructor() {
super();
}
connectedCallback() {
super.connectedCallback();
this.addValidator(
'rangeUnderflow',
() => this.minMessage,
() => !!this.min && this.#pickerContext.getSelection().length < this.min,
);
this.addValidator(
'rangeOverflow',
() => this.maxMessage,
() => !!this.max && this.#pickerContext.getSelection().length > this.max,
);
this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(',')));
this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems));
}
protected _openPicker() {
this.#pickerContext.openPicker({
hideTreeRoot: true,
});
}
protected getFormElement() {
return undefined;
}
render() {
return html`
${this.#renderItems()}
${this.#renderAddButton()}
`;
}
#renderItems() {
if (!this._items) return;
// TODO: Add sorting. [LK]
return html`
<uui-ref-list
>${repeat(
this._items,
(item) => item.id,
(item) => this._renderItem(item),
)}</uui-ref-list
>
`;
}
#renderAddButton() {
if (this.max > 0 && this.selectedIds.length >= this.max) return;
return html`
<uui-button
id="add-button"
look="placeholder"
@click=${this._openPicker}
label="${this.localize.term('general_choose')}"
>${this.localize.term('general_choose')}</uui-button
>
`;
}
private _renderItem(item: MemberTypeItemResponseModel) {
if (!item.id) return;
return html`
<uui-ref-node-document-type name=${ifDefined(item.name)}>
<uui-action-bar slot="actions">
<uui-button
@click=${() => this.#pickerContext.requestRemoveItem(item.id!)}
label="Remove Member Type ${item.name}"
>${this.localize.term('general_remove')}</uui-button
>
</uui-action-bar>
</uui-ref-node-document-type>
`;
}
static styles = [
css`
#add-button {
width: 100%;
}
`,
];
}
export default UmbMemberTypeInputElement;
declare global {
interface HTMLElementTagNameMap {
'umb-input-member-type': UmbMemberTypeInputElement;
}
}

View File

@@ -0,0 +1,5 @@
import './components/index.js';
export * from './components/index.js';
export * from './repository/index.js';
export * from './entity.js';

View File

@@ -1,13 +1,15 @@
import { manifests as entityActionsManifests } from './entity-actions/manifests.js';
import { manifests as menuItemManifests } from './menu-item/manifests.js';
import { manifests as treeManifests } from './tree/manifests.js';
import { manifests as repositoryManifests } from './repository/manifests.js';
import { manifests as treeManifests } from './tree/manifests.js';
import { manifests as workspaceManifests } from './workspace/manifests.js';
import { manifests as entityActionManifests } from './entity-actions/manifests.js';
import './components/index.js';
export const manifests = [
...entityActionsManifests,
...menuItemManifests,
...treeManifests,
...repositoryManifests,
...treeManifests,
...workspaceManifests,
...entityActionManifests,
];

View File

@@ -1 +1,2 @@
export { UmbMemberTypeRepository } from './member-type.repository.js';
export * from './member-type.repository.js';
export * from './manifests.js';

View File

@@ -0,0 +1,3 @@
import './input-member/input-member.element.js';
export * from './input-member/input-member.element.js';

View File

@@ -0,0 +1,171 @@
import { css, html, customElement, property, state, ifDefined, repeat } from '@umbraco-cms/backoffice/external/lit';
import { FormControlMixin } from '@umbraco-cms/backoffice/external/uui';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import type { MemberItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
import { splitStringToArray } from '@umbraco-cms/backoffice/utils';
@customElement('umb-input-member')
export class UmbInputMemberElement extends FormControlMixin(UmbLitElement) {
/**
* This is a minimum amount of selected items in this input.
* @type {number}
* @attr
* @default 0
*/
@property({ type: Number })
public get min(): number {
//return this.#pickerContext.min;
return 0;
}
public set min(value: number) {
//this.#pickerContext.min = value;
}
/**
* Min validation message.
* @type {boolean}
* @attr
* @default
*/
@property({ type: String, attribute: 'min-message' })
minMessage = 'This field need more items';
/**
* This is a maximum amount of selected items in this input.
* @type {number}
* @attr
* @default Infinity
*/
@property({ type: Number })
public get max(): number {
//return this.#pickerContext.max;
return Infinity;
}
public set max(value: number) {
//this.#pickerContext.max = value;
}
/**
* Max validation message.
* @type {boolean}
* @attr
* @default
*/
@property({ type: String, attribute: 'min-message' })
maxMessage = 'This field exceeds the allowed amount of items';
public get selectedIds(): Array<string> {
//return this.#pickerContext.getSelection();
return [];
}
public set selectedIds(ids: Array<string>) {
//this.#pickerContext.setSelection(ids);
}
@property()
public set value(idsString: string) {
// Its with full purpose we don't call super.value, as thats being handled by the observation of the context selection.
this.selectedIds = splitStringToArray(idsString);
}
@state()
private _items?: Array<MemberItemResponseModel>;
// TODO: Create the `UmbMemberPickerContext` [LK]
//#pickerContext = new UmbMemberPickerContext(this);
constructor() {
super();
// this.addValidator(
// 'rangeUnderflow',
// () => this.minMessage,
// () => !!this.min && this.#pickerContext.getSelection().length < this.min,
// );
// this.addValidator(
// 'rangeOverflow',
// () => this.maxMessage,
// () => !!this.max && this.#pickerContext.getSelection().length > this.max,
// );
// this.observe(this.#pickerContext.selection, (selection) => (super.value = selection.join(',')));
// this.observe(this.#pickerContext.selectedItems, (selectedItems) => (this._items = selectedItems));
}
protected _openPicker() {
console.log("member.openPicker");
// this.#pickerContext.openPicker({
// hideTreeRoot: true,
// });
}
protected _requestRemoveItem(item: MemberItemResponseModel){
console.log("member.requestRemoveItem", item);
//this.#pickerContext.requestRemoveItem(item.id!);
}
protected getFormElement() {
return undefined;
}
render() {
return html`
${this.#renderItems()}
${this.#renderAddButton()}
`;
}
#renderItems() {
if (!this._items) return;
// TODO: Add sorting. [LK]
return html`<uui-ref-list
>${repeat(
this._items,
(item) => item.id,
(item) => this._renderItem(item),
)}
</uui-ref-list>`;
}
#renderAddButton() {
if (this.max > 0 && this.selectedIds.length >= this.max) return;
return html`<uui-button
id="add-button"
look="placeholder"
@click=${this._openPicker}
label=${this.localize.term('general_add')}></uui-button>`;
}
private _renderItem(item: MemberItemResponseModel) {
if (!item.id) return;
return html`
<uui-ref-node name=${ifDefined(item.name)} detail=${ifDefined(item.id)}>
<!-- TODO: implement is deleted <uui-tag size="s" slot="tag" color="danger">Deleted</uui-tag> -->
<uui-action-bar slot="actions">
<uui-button
@click=${() => this._requestRemoveItem(item)}
label="Remove member ${item.name}"
>Remove</uui-button
>
</uui-action-bar>
</uui-ref-node>
`;
}
static styles = [
css`
#add-button {
width: 100%;
}
`,
];
}
export default UmbInputMemberElement;
declare global {
interface HTMLElementTagNameMap {
'umb-input-member': UmbInputMemberElement;
}
}

View File

@@ -0,0 +1,14 @@
import { Meta, StoryObj } from '@storybook/web-components';
import './input-member.element.js';
import type { UmbInputMemberElement } from './input-member.element.js';
const meta: Meta<UmbInputMemberElement> = {
title: 'Components/Inputs/Member',
component: 'umb-input-member',
};
export default meta;
type Story = StoryObj<UmbInputMemberElement>;
export const Overview: Story = {
args: {},
};

View File

@@ -0,0 +1,20 @@
import { expect, fixture, html } from '@open-wc/testing';
import { UmbInputMemberElement } from './input-member.element.js';
import { defaultA11yConfig } from '@umbraco-cms/internal/test-utils';
describe('UmbInputMemberElement', () => {
let element: UmbInputMemberElement;
beforeEach(async () => {
element = await fixture(html` <umb-input-member></umb-input-member> `);
});
it('is defined with its own instance', () => {
expect(element).to.be.instanceOf(UmbInputMemberElement);
});
if ((window as any).__UMBRACO_TEST_RUN_A11Y_TEST) {
it('passes the a11y audit', async () => {
await expect(element).shadowDom.to.be.accessible(defaultA11yConfig);
});
}
});

View File

@@ -1 +1,4 @@
import './components/index.js';
export * from './components/index.js';
export * from './repository/index.js';

View File

@@ -8,8 +8,8 @@ export class UmbLanguageCreateEntityAction extends UmbEntityActionBase<UmbLangua
super(host, repositoryAlias, unique);
}
// TODO: Generate the href or retrieve it from something?
async getHref() {
return 'section/settings/workspace/language/create';
async execute() {
// TODO: Generate the href or retrieve it from something?
history.pushState(null, '', `section/settings/workspace/language/create`);
}
}

View File

@@ -57,7 +57,7 @@ export class UmbSelectionManager extends UmbBaseController {
public setSelection(value: Array<string | null>) {
if (this.getSelectable() === false) return;
if (value === undefined) throw new Error('Value cannot be undefined');
const newSelection = this.getMultiple() ? value : [value[0]];
const newSelection = this.getMultiple() ? value : value.slice(0, 1);
this.#selection.next(newSelection);
}
@@ -78,7 +78,7 @@ export class UmbSelectionManager extends UmbBaseController {
public setMultiple(value: boolean) {
this.#multiple.next(value);
/* If multiple is set to false, and the current selection is more than one,
/* If multiple is set to false, and the current selection is more than one,
then we need to set the selection to the first item. */
if (value === false && this.getSelection().length > 1) {
this.setSelection([this.getSelection()[0]]);