linkpicker
This commit is contained in:
@@ -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``;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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>`;
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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>`;
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
@@ -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',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user