linkpicker

This commit is contained in:
Lone Iversen
2023-02-16 13:09:32 +01:00
parent 758ad2f5bf
commit c9e27de687
7 changed files with 161 additions and 146 deletions

View File

@@ -4,14 +4,19 @@ import { customElement, property, state } from 'lit/decorators.js';
import { ifDefined } from 'lit-html/directives/if-defined.js';
import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins';
import { UmbLitElement } from '@umbraco-cms/element';
import { DocumentTreeItemModel, FolderTreeItemModel } from '@umbraco-cms/backend-api';
import { DocumentTreeItemModel, EntityTreeItemModel, FolderTreeItemModel } from '@umbraco-cms/backend-api';
import { UmbModalService, UMB_MODAL_SERVICE_CONTEXT_TOKEN } from '@umbraco-cms/modal';
import { UmbObserverController } from '@umbraco-cms/observable-api';
export type OverlaySize = 'small' | 'medium' | 'large';
export interface Url {
title: string;
href: string;
target: boolean;
export interface Link {
icon?: string;
name?: string;
published?: boolean;
queryString?: string;
target?: string;
trashed?: boolean;
udi?: string;
url?: string;
}
@customElement('umb-input-multi-url-picker')
@@ -70,108 +75,81 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen
@property({ type: Boolean, attribute: 'hide-anchor' })
hideAnchor?: boolean;
@property()
ignoreUserStartNodes?: boolean;
/**
* @type {"small" | "medium" | "large"}
* @attr
* @default "small"
*/
@property()
overlaySize: OverlaySize = 'small';
// TODO: do we need both selectedKeys and value? If we just use value we follow the same pattern as native form controls.
private _selectedKeys: Array<string> = [];
public get selectedKeys(): Array<string> {
return this._selectedKeys;
}
public set selectedKeys(keys: Array<string>) {
this._selectedKeys = keys;
super.value = keys.join(',');
}
overlaySize?: 'small' | 'medium' | 'large' | 'full';
@property()
public set value(keysString: string) {
if (keysString !== this._value) {
this.selectedKeys = keysString.split(/[ ,]+/);
}
}
@property()
target = false;
@property()
title = '';
@property()
url = '';
@state()
private _urls?: Url[] = [
{
title: 'Cake',
href: 'google.com',
target: true,
},
];
@state()
private _items?: Array<DocumentTreeItemModel>;
links: Array<Link> = [];
private _modalService?: UmbModalService;
private _pickedItemsObserver?: UmbObserverController<FolderTreeItemModel>;
constructor() {
super();
this.addValidator(
'rangeUnderflow',
() => this.minMessage,
() => !!this.min && this._selectedKeys.length < this.min
);
this.addValidator(
'rangeOverflow',
() => this.maxMessage,
() => !!this.max && this._selectedKeys.length > this.max
);
this.consumeContext(UMB_MODAL_SERVICE_CONTEXT_TOKEN, (instance) => {
this._modalService = instance;
});
}
private _openPicker() {
const modalHandler = this._modalService?.multiUrlPicker({ treeItem: this._selectedKeys[0], target: this.target });
modalHandler?.onClose().then(({ selection }: any) => {
this.selectedKeys[0] = selection.treeItem;
this.target = selection.target;
this.url = selection.href;
private async _observePickedDocumentsOrMedias() {
this._pickedItemsObserver?.destroy();
}
private _removeItem(index: number) {
this.links.splice(index, 1);
this.requestUpdate();
}
private _openPicker(data?: Link, index?: number) {
const modalHandler = this._modalService?.linkPicker(
{
name: data?.name || undefined,
published: data?.published || undefined,
queryString: data?.queryString || undefined,
target: data?.target || undefined,
trashed: data?.trashed || undefined,
udi: data?.udi || undefined,
url: data?.url || undefined,
},
{
hideAnchor: this.hideAnchor,
ignoreUserStartNodes: this.ignoreUserStartNodes,
overlaySize: this.overlaySize || 'small',
}
);
modalHandler?.onClose().then((newUrl: Link) => {
if (!newUrl) return;
if (index !== undefined && index >= 0) this.links[index] = newUrl;
else this.links.push(newUrl);
this.requestUpdate();
});
}
render() {
return html`${this._urls?.map((url) => this._renderItem(url))}
return html`${this.links?.map((link, index) => this._renderItem(link, index))}
<uui-button look="placeholder" label="Add" @click=${this._openPicker}>Add</uui-button>`;
}
private _renderItem(url: Url) {
return html`<uui-ref-node name="Title" detail="Url">
<uui-icon slot="icon" name="${this.target}umb:link"></uui-icon>
<uui-action-bar slot="actions"> </uui-action-bar>
private _renderItem(link: Link, index: number) {
return html`<uui-ref-node
@open="${() => this._openPicker(link, index)}"
.name="${link.name || ''}"
.detail="${(link.url || '') + (link.queryString || '')}">
<uui-icon slot="icon" name="${link.icon || 'umb:link'}"></uui-icon>
<uui-action-bar slot="actions">
<uui-button @click="${() => this._removeItem(index)}" label="Remove link">Remove</uui-button>
</uui-action-bar>
</uui-ref-node>`;
// TODO: remove when we have a way to handle trashed items
//const tempItem = item as FolderTreeItemModel & { isTrashed: boolean };
/*
return html`
<uui-ref-node name=${ifDefined(item.name === null ? undefined : item.name)} detail=${ifDefined(item.key)}>
${tempItem.isTrashed ? html` <uui-tag size="s" slot="tag" color="danger">Trashed</uui-tag> ` : nothing}
<uui-action-bar slot="actions"> </uui-action-bar>
</uui-ref-node>
`;
*/
}
private _renderItemIcon(url: Url) {
if (url.href === 'key') return html``;
if (url.target === true) return html``;
}
}

View File

@@ -2,7 +2,6 @@ import { html } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { UUIColorSwatchesEvent } from '@umbraco-ui/uui';
import '../../../../shared/components/color-picker/color-picker.element';
import { UmbLitElement } from '@umbraco-cms/element';
import type { DataTypePropertyModel } from '@umbraco-cms/backend-api';

View File

@@ -3,9 +3,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement, property, state } from 'lit/decorators.js';
import { ifDefined } from 'lit-html/directives/if-defined.js';
import { UmbInputMultiUrlPickerElement } from '../../../components/input-multi-url-picker/input-multi-url-picker.element';
import type { OverlaySize } from '../../../components/input-multi-url-picker/input-multi-url-picker.element';
import { UmbLitElement } from '@umbraco-cms/element';
import type { DataTypePropertyData } from '@umbraco-cms/models';
/**
* @element umb-property-editor-ui-multi-url-picker
@@ -22,7 +20,7 @@ export class UmbPropertyEditorUIMultiUrlPickerElement extends UmbLitElement {
public set value(value: string[]) {
this._value = value || [];
}
/*
@property({ type: Array, attribute: false })
public set config(config: DataTypePropertyData[]) {
const overlaySize = config.find((x) => x.alias === 'overlaySize');
@@ -30,22 +28,19 @@ export class UmbPropertyEditorUIMultiUrlPickerElement extends UmbLitElement {
const hideAnchor = config.find((x) => x.alias === 'hideAnchor');
if (hideAnchor) this._hideAnchor = hideAnchor.value;
}
}*/
@state()
private _overlaySize?: OverlaySize;
@state()
private _hideAnchor?: boolean;
private _onChange(event: CustomEvent) {
this._value = (event.target as UmbInputMultiUrlPickerElement).selectedKeys;
//this._value = (event.target as UmbInputMultiUrlPickerElement);
this.dispatchEvent(new CustomEvent('property-value-change'));
}
render() {
return html`<umb-input-multi-url-picker
@change="${this._onChange}"
overlaySize="${ifDefined(this._overlaySize)}"
?hide-anchor="${this._hideAnchor}"
.selectedKeys="${this._value}"></umb-input-multi-url-picker>`;
}

View File

@@ -119,7 +119,24 @@ export const data: Array<DataTypeModel & { type: 'data-type' }> = [
parentKey: null,
propertyEditorAlias: 'Umbraco.MultiUrlPicker',
propertyEditorUiAlias: 'Umb.PropertyEditorUI.MultiUrlPicker',
data: [],
data: [
{
alias: 'hideAnchor',
value: false,
},
{
alias: 'ignoreUserStartNodes',
value: false,
},
{
alias: 'maxNumber',
value: null,
},
{
alias: 'minNumber',
value: null,
},
],
},
{
type: 'data-type',

View File

@@ -1,23 +1,28 @@
import { css, html } from 'lit';
import { css, html, nothing } from 'lit';
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
import { customElement, query, state } from 'lit/decorators.js';
import { UUIBooleanInputEvent, UUIInputElement, UUIToggleElement } from '@umbraco-ui/uui';
import { UUIBooleanInputEvent, UUIInputElement } from '@umbraco-ui/uui';
import { UmbModalLayoutElement } from '../modal-layout.element';
export interface UmbModalMultiUrlPickerData {
UrlString?: string;
anchorString?: string;
linkTitle?: string;
target?: boolean;
hideAnchor?: boolean;
treeItem?: string;
ignoreUserStartNodes?: boolean;
}
import { UmbTreeElement } from '../../../../backoffice/shared/components/tree/tree.element';
export interface LinkPickerData {
icon?: string;
name?: string;
published?: boolean;
queryString?: string;
target?: string;
trashed?: boolean;
udi?: string;
url?: string;
}
export interface LinkPickerConfig {
hideAnchor?: boolean;
ignoreUserStartNodes?: boolean;
overlaySize?: 'small' | 'medium' | 'large' | 'full';
}
@customElement('umb-modal-layout-multi-url-picker')
export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement<UmbModalMultiUrlPickerData> {
export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement<LinkPickerData & LinkPickerConfig> {
static styles = [
UUITextStyles,
css`
@@ -53,16 +58,19 @@ export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement<U
];
@state()
_url = {
title: '',
href: '',
anchor: '',
target: false,
treeItem: '',
_link: LinkPickerData = {
icon: undefined,
name: undefined,
published: true,
queryString: undefined,
target: undefined,
trashed: false,
udi: undefined,
url: undefined,
};
@state()
_layout = {
_layout: LinkPickerConfig = {
hideAnchor: false,
ignoreUserStartNodes: false,
};
@@ -71,36 +79,45 @@ export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement<U
private _linkInput!: UUIInputElement;
@query('#anchor-input')
private _anchorInput?: UUIInputElement;
private _linkQueryInput?: UUIInputElement;
@query('#link-title')
@query('#link-title-input')
private _linkTitleInput!: UUIInputElement;
@query('#target-toggle')
private _targetToggle!: UUIToggleElement;
connectedCallback() {
super.connectedCallback();
this._url.href = this.data?.UrlString ?? '';
this._url.anchor = this.data?.anchorString ?? '';
this._url.title = this.data?.linkTitle ?? '';
this._url.target = this.data?.target ?? false;
this._url.treeItem = this.data?.treeItem ?? '';
this._layout.hideAnchor = this.data?.hideAnchor ?? false;
this._layout.ignoreUserStartNodes = this.data?.ignoreUserStartNodes ?? false;
this._link.icon = this.data?.icon;
this._link.name = this.data?.name;
this._link.published = this.data?.published ?? true;
this._link.queryString = this.data?.queryString;
this._link.target = this.data?.target;
this._link.trashed = this.data?.trashed ?? false;
this._link.udi = this.data?.udi;
this._link.url = this.data?.url;
this._layout.hideAnchor = this.data?.hideAnchor;
this._layout.ignoreUserStartNodes = this.data?.ignoreUserStartNodes;
}
private _handleQueryString() {
if (!this._linkQueryInput) return;
const query = this._linkQueryInput.value as string;
//TODO: Handle query strings (add # etc)
this._link.queryString = query;
}
private _handleSelectionChange(e: CustomEvent) {
e.stopPropagation();
const element = e.target as UmbTreeElement;
this._url.treeItem = element.selection[element.selection.length - 1];
const udi = element.selection[element.selection.length - 1];
this._link.udi = udi;
//TODO: Update icon, if published, if trashed, url
this._link.url = udi;
this.requestUpdate();
}
private _submit() {
this.modalHandler?.close({
selection: this._url,
});
this.modalHandler?.close(this._link);
}
private _close() {
@@ -118,15 +135,18 @@ export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement<U
id="link-title-input"
placeholder="Enter a title"
label="link title"
.value="${this._url.title}"></uui-input>
@input=${() => (this._link.name = this._linkTitleInput.value as string)}
.value="${this._link.name ?? ''}"></uui-input>
<uui-label>Target</uui-label>
<uui-toggle
id="#target-toggle"
.checked="${this._url.target}"
@change="${(e: UUIBooleanInputEvent) => (this._url.target = e.target.checked)}"
>Open the link in a new tab</uui-toggle
>
label="Toggle if link should open in a new tab"
.checked="${this._link.target === '_blank' ? true : false}"
@change="${(e: UUIBooleanInputEvent) =>
e.target.checked ? (this._link.target = '_blank') : (this._link.target = '')}">
Open the link in a new tab
</uui-toggle>
<hr />
@@ -147,30 +167,32 @@ export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement<U
id="link-input"
placeholder="URL"
label="URL"
.value="${this._url.treeItem}"
.disabled="${this._url.treeItem ? true : false}"></uui-input>
.value="${this._link.udi ?? this._link.url ?? ''}"
@input=${() => (this._link.url = this._linkInput.value as string)}
.disabled="${this._link.udi ? true : false}"></uui-input>
</span>`;
}
private _renderAnchorInput() {
if (this._layout.hideAnchor) return;
if (this._layout.hideAnchor) return nothing;
return html`<span>
<uui-label for="anchor-input">Anchor / querystring</uui-label>
<uui-input
id="anchor-input"
placeholder="#value or ?key=value"
label="#value or ?key=value"
.value="${this._url.anchor}"></uui-input>
@input=${this._handleQueryString}
.value="${this._link.queryString ?? ''}"></uui-input>
</span>`;
}
private _renderTrees() {
return html`<uui-label for="search-input">Link to page</uui-label>
<uui-input id="search-input" placeholder="Type to search"></uui-input>
<uui-input id="search-input" placeholder="Type to search" label="Type to search"></uui-input>
<umb-tree
alias="Umb.Tree.Documents"
@selected=${this._handleSelectionChange}
.selection=${[this._url.treeItem]}
.selection=${[this._link.udi ?? '']}
selectable></umb-tree>
<hr />
@@ -180,7 +202,7 @@ export class UmbModalLayoutMultiUrlPickerElement extends UmbModalLayoutElement<U
<umb-tree
alias="Umb.Tree.Media"
@selected=${this._handleSelectionChange}
.selection=${[this._url.treeItem]}
.selection=${[this._link.udi ?? '']}
selectable></umb-tree>`;
}
}

View File

@@ -1,5 +1,5 @@
import '../../../../backoffice/shared/components/body-layout/body-layout.element';
import './modal-layout-multi-url-picker.element';
import './modal-layout-link-picker.element';
import { Meta, Story } from '@storybook/web-components';
import { html } from 'lit';
@@ -7,7 +7,7 @@ import { html } from 'lit';
import type {
UmbModalLayoutMultiUrlPickerElement,
UmbModalMultiUrlPickerData,
} from './modal-layout-multi-url-picker.element';
} from './modal-layout-link-picker.element';
export default {
title: 'API/Modals/Layouts/Multi Url Picker',

View File

@@ -5,7 +5,7 @@ import './layouts/media-picker/modal-layout-media-picker.element';
import './layouts/property-editor-ui-picker/modal-layout-property-editor-ui-picker.element';
import './layouts/modal-layout-current-user.element';
import './layouts/icon-picker/modal-layout-icon-picker.element';
import './layouts/multi-url-picker/modal-layout-multi-url-picker.element';
import './layouts/link-picker/modal-layout-link-picker.element';
import { UUIModalSidebarSize } from '@umbraco-ui/uui-modal-sidebar';
import { BehaviorSubject } from 'rxjs';
@@ -15,7 +15,7 @@ import type { UmbModalConfirmData } from './layouts/confirm/modal-layout-confirm
import type { UmbModalContentPickerData } from './layouts/content-picker/modal-layout-content-picker.element';
import type { UmbModalPropertyEditorUIPickerData } from './layouts/property-editor-ui-picker/modal-layout-property-editor-ui-picker.element';
import type { UmbModalMediaPickerData } from './layouts/media-picker/modal-layout-media-picker.element';
import type { UmbModalMultiUrlPickerData } from './layouts/multi-url-picker/modal-layout-multi-url-picker.element';
import type { LinkPickerConfig, LinkPickerData } from './layouts/link-picker/modal-layout-link-picker.element';
import { UmbModalHandler } from './modal-handler';
import { UmbContextToken } from '@umbraco-cms/context-api';
@@ -95,8 +95,12 @@ export class UmbModalService {
* @return {*} {UmbModalHandler}
* @memberof UmbModalService
*/
public multiUrlPicker(data?: UmbModalMultiUrlPickerData): UmbModalHandler {
return this.open('umb-modal-layout-multi-url-picker', { data, type: 'sidebar', size: 'small' });
public linkPicker(data?: LinkPickerData, config?: LinkPickerConfig): UmbModalHandler {
return this.open('umb-modal-layout-multi-url-picker', {
data,
type: 'sidebar',
size: config?.overlaySize || 'small',
});
}
/**