POC Modal deeplinking
This commit is contained in:
@@ -13,7 +13,7 @@ import { ManifestModal } from '@umbraco-cms/extensions-registry';
|
||||
/**
|
||||
* Type which omits the real submit method, and replaces it with a submit method which accepts an optional argument depending on the generic type.
|
||||
*/
|
||||
export type UmbModalHandler<ModalData = unknown, ModalResult = unknown> = Omit<
|
||||
export type UmbModalHandler<ModalData extends object = { [key: string]: unknown }, ModalResult = unknown> = Omit<
|
||||
UmbModalHandlerClass<ModalData, ModalResult>,
|
||||
'submit'
|
||||
> &
|
||||
@@ -35,7 +35,7 @@ type OptionalSubmitArgumentIfUndefined<T> = T extends undefined
|
||||
};
|
||||
|
||||
//TODO consider splitting this into two separate handlers
|
||||
export class UmbModalHandlerClass<ModalData, ModalResult> {
|
||||
export class UmbModalHandlerClass<ModalData extends object, ModalResult> {
|
||||
private _submitPromise: Promise<ModalResult>;
|
||||
private _submitResolver?: (value: ModalResult) => void;
|
||||
private _submitRejecter?: () => void;
|
||||
|
||||
@@ -76,7 +76,7 @@ export class UmbModalContext {
|
||||
* @return {*} {UmbModalHandler}
|
||||
* @memberof UmbModalContext
|
||||
*/
|
||||
public open<ModalData = unknown, ModalResult = unknown>(
|
||||
public open<ModalData extends object = { [key: string]: unknown }, ModalResult = unknown>(
|
||||
modalAlias: string | UmbModalToken<ModalData, ModalResult>,
|
||||
data?: ModalData,
|
||||
config?: UmbModalConfig
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { UmbModalConfig } from '../modal.context';
|
||||
|
||||
export class UmbModalToken<Data = unknown, Result = unknown> {
|
||||
export class UmbModalToken<Data extends object = { [key: string]: unknown }, Result = unknown> {
|
||||
/**
|
||||
* Get the data type of the token's data.
|
||||
*
|
||||
|
||||
@@ -3,10 +3,9 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins';
|
||||
import { UUIModalSidebarSize } from '@umbraco-ui/uui-modal-sidebar';
|
||||
import { UmbRouteContext, UMB_ROUTE_CONTEXT_TOKEN } from '@umbraco-cms/router';
|
||||
import { UmbModalRouteBuilder, UmbRouteContext, UMB_ROUTE_CONTEXT_TOKEN } from '@umbraco-cms/router';
|
||||
import { UmbLinkPickerLink, UMB_LINK_PICKER_MODAL_TOKEN } from '../../modals/link-picker';
|
||||
import { UmbLitElement } from '@umbraco-cms/element';
|
||||
import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/modal';
|
||||
|
||||
/**
|
||||
* @element umb-input-multi-url-picker
|
||||
@@ -88,7 +87,7 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen
|
||||
@property({ attribute: false })
|
||||
set urls(data: Array<UmbLinkPickerLink>) {
|
||||
if (!data) return;
|
||||
this._urls = data;
|
||||
this._urls = [...data]; // Unfreeze data coming from State, so we can manipulate it.
|
||||
super.value = this._urls.map((x) => x.url).join(',');
|
||||
}
|
||||
|
||||
@@ -97,9 +96,11 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen
|
||||
}
|
||||
|
||||
private _urls: Array<UmbLinkPickerLink> = [];
|
||||
private _modalContext?: UmbModalContext;
|
||||
//private _modalContext?: UmbModalContext;
|
||||
private _routeContext?: UmbRouteContext;
|
||||
|
||||
private _linkPickerURL?: UmbModalRouteBuilder;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.addValidator(
|
||||
@@ -113,9 +114,11 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen
|
||||
() => !!this.max && this.urls.length > this.max
|
||||
);
|
||||
|
||||
/*
|
||||
this.consumeContext(UMB_MODAL_CONTEXT_TOKEN, (instance) => {
|
||||
this._modalContext = instance;
|
||||
});
|
||||
*/
|
||||
|
||||
this.consumeContext(UMB_ROUTE_CONTEXT_TOKEN, (instance) => {
|
||||
this._routeContext = instance;
|
||||
@@ -123,16 +126,29 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen
|
||||
// Registre the routes of this UI:
|
||||
// TODO: To avoid retriving the property alias, we might make use of the property context?
|
||||
// Or maybe its not the property-alias, but something unique? as this might not be in a property?.
|
||||
this._routeContext.registerModal(UMB_LINK_PICKER_MODAL_TOKEN, {
|
||||
//path: `${'to-do-myPropertyAlias'}/:index`,
|
||||
path: `modal`,
|
||||
onSetup: (routeInfo) => {
|
||||
this._linkPickerURL = this._routeContext.registerModal(UMB_LINK_PICKER_MODAL_TOKEN, {
|
||||
path: `${'to-do-myPropertyAlias'}/:index`,
|
||||
onSetup: (routingInfo) => {
|
||||
// Get index from routeInfo:
|
||||
const index = 0;
|
||||
const indexParam = routingInfo.match.params.index;
|
||||
if (!indexParam) return false;
|
||||
let index: number | null = parseInt(routingInfo.match.params.index);
|
||||
if (Number.isNaN(index)) return false;
|
||||
|
||||
// Use the index to find data:
|
||||
console.log('onSetup modal', routeInfo);
|
||||
/*
|
||||
modaldata = {
|
||||
console.log('onSetup modal index:', index);
|
||||
|
||||
let data: UmbLinkPickerLink | null = null;
|
||||
if (index >= 0 && index < this.urls.length) {
|
||||
data = this._getItemByIndex(index);
|
||||
} else {
|
||||
index = null;
|
||||
}
|
||||
|
||||
console.log('onSetup modal got data:', data);
|
||||
|
||||
const modalData = {
|
||||
index: index,
|
||||
link: {
|
||||
name: data?.name,
|
||||
published: data?.published,
|
||||
@@ -148,14 +164,12 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen
|
||||
overlaySize: this.overlaySize || 'small',
|
||||
},
|
||||
};
|
||||
return modaldata;
|
||||
*/
|
||||
return modalData;
|
||||
},
|
||||
onSubmit: (newUrl) => {
|
||||
if (!newUrl) return;
|
||||
|
||||
const index = 0; // TODO: get the index in some way?.
|
||||
this._setSelection(newUrl, index);
|
||||
onSubmit: (submitData) => {
|
||||
console.log('On submit in property editor input');
|
||||
if (!submitData) return;
|
||||
this._setSelection(submitData.link, submitData.index);
|
||||
},
|
||||
onReject: () => {
|
||||
console.log('User cancelled dialog.');
|
||||
@@ -169,9 +183,16 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen
|
||||
this._dispatchChangeEvent();
|
||||
}
|
||||
|
||||
private _setSelection(selection: UmbLinkPickerLink, index?: number) {
|
||||
if (index !== undefined && index >= 0) this.urls[index] = selection;
|
||||
else this.urls.push(selection);
|
||||
private _getItemByIndex(index: number) {
|
||||
return this.urls[index];
|
||||
}
|
||||
|
||||
private _setSelection(selection: UmbLinkPickerLink, index: number | null) {
|
||||
if (index !== null && index >= 0) {
|
||||
this.urls[index] = selection;
|
||||
} else {
|
||||
this.urls.push(selection);
|
||||
}
|
||||
|
||||
this._dispatchChangeEvent();
|
||||
}
|
||||
@@ -182,6 +203,8 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen
|
||||
}
|
||||
|
||||
private _openPicker(data?: UmbLinkPickerLink, index?: number) {
|
||||
console.log('JS open picker, should fail for now,');
|
||||
/*
|
||||
const modalHandler = this._modalContext?.open(UMB_LINK_PICKER_MODAL_TOKEN, {
|
||||
link: {
|
||||
name: data?.name,
|
||||
@@ -203,11 +226,13 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen
|
||||
|
||||
this._setSelection(newUrl, index);
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`${this.urls?.map((link, index) => this._renderItem(link, index))}
|
||||
<uui-button look="placeholder" label="Add" @click=${this._openPicker}>Add</uui-button>`;
|
||||
<uui-button look="placeholder" label="Add" .href=${this._linkPickerURL?.({ index: -1 })}>Add</uui-button>`;
|
||||
// "modal/Umb.Modal.LinkPicker/${'to-do-myPropertyAlias'}/-1"
|
||||
}
|
||||
|
||||
private _renderItem(link: UmbLinkPickerLink, index: number) {
|
||||
@@ -217,10 +242,11 @@ export class UmbInputMultiUrlPickerElement extends FormControlMixin(UmbLitElemen
|
||||
@open="${() => this._openPicker(link, index)}">
|
||||
<uui-icon slot="icon" name="${link.icon || 'umb:link'}"></uui-icon>
|
||||
<uui-action-bar slot="actions">
|
||||
<uui-button @click="${() => this._openPicker(link, index)}" label="Edit link">Edit</uui-button>
|
||||
<uui-button .href=${this._linkPickerURL?.({ index })} label="Edit link">Edit</uui-button>
|
||||
<uui-button @click="${() => this._removeItem(index)}" label="Remove link">Remove</uui-button>
|
||||
</uui-action-bar>
|
||||
</uui-ref-node>`;
|
||||
// "modal/Umb.Modal.LinkPicker/${'to-do-myPropertyAlias'}/${index}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,12 @@ import { UUIModalSidebarSize } from '@umbraco-ui/uui-modal-sidebar';
|
||||
import { UmbModalToken } from '@umbraco-cms/modal';
|
||||
|
||||
export interface UmbLinkPickerModalData {
|
||||
index: number | null;
|
||||
link: UmbLinkPickerLink;
|
||||
config: UmbLinkPickerConfig;
|
||||
}
|
||||
|
||||
export type UmbLinkPickerModalResult = UmbLinkPickerLink;
|
||||
export type UmbLinkPickerModalResult = { index: number | null; link: UmbLinkPickerLink };
|
||||
|
||||
export interface UmbLinkPickerLink {
|
||||
icon?: string | null;
|
||||
|
||||
@@ -46,6 +46,9 @@ export class UmbLinkPickerModalElement extends UmbModalBaseElement<UmbLinkPicker
|
||||
@state()
|
||||
_selectedKey?: string;
|
||||
|
||||
@state()
|
||||
_index: number | null = null;
|
||||
|
||||
@state()
|
||||
_link: UmbLinkPickerLink = {
|
||||
icon: null,
|
||||
@@ -76,6 +79,7 @@ export class UmbLinkPickerModalElement extends UmbModalBaseElement<UmbLinkPicker
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (!this.data) return;
|
||||
this._index = this.data?.index;
|
||||
this._link = this.data?.link;
|
||||
this._layout = this.data?.config;
|
||||
|
||||
@@ -105,7 +109,7 @@ export class UmbLinkPickerModalElement extends UmbModalBaseElement<UmbLinkPicker
|
||||
}
|
||||
|
||||
private _submit() {
|
||||
this.modalHandler?.submit(this._link);
|
||||
this.modalHandler?.submit({ index: this._index, link: this._link });
|
||||
}
|
||||
|
||||
private _close() {
|
||||
|
||||
@@ -53,6 +53,7 @@ export class UmbPropertyEditorUIMultiUrlPickerElement extends UmbLitElement impl
|
||||
|
||||
private _onChange(event: CustomEvent) {
|
||||
this.value = (event.target as UmbInputMultiUrlPickerElement).urls;
|
||||
console.log(this.value);
|
||||
this.dispatchEvent(new CustomEvent('property-value-change'));
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,17 @@ export const data: Array<DocumentModel> = [
|
||||
alias: 'multiUrlPicker',
|
||||
culture: null,
|
||||
segment: null,
|
||||
value: null,
|
||||
value: [
|
||||
{
|
||||
name: undefined,
|
||||
published: undefined,
|
||||
queryString: undefined,
|
||||
target: undefined,
|
||||
trashed: undefined,
|
||||
udi: 'umb://document/c05da24d7740447b9cdcbd8ce2172e38',
|
||||
url: 'umb://document/c05da24d7740447b9cdcbd8ce2172e38',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
alias: 'multiNodeTreePicker',
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
import { IRoute } from 'router-slot';
|
||||
import { IRoute, IRoutingInfo, PARAM_IDENTIFIER, stripSlash } from 'router-slot';
|
||||
import { UmbContextConsumerController, UmbContextProviderController, UmbContextToken } from '@umbraco-cms/context-api';
|
||||
import { UmbControllerHostInterface } from '@umbraco-cms/controller';
|
||||
import { UmbModalToken } from '@umbraco-cms/modal';
|
||||
|
||||
// Get the second generic type of UmbModalToken:
|
||||
type GetResultType<T> = T extends UmbModalToken<infer Data, infer Result> ? Result : unknown;
|
||||
import { UmbModalToken, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/modal';
|
||||
|
||||
const EmptyDiv = document.createElement('div');
|
||||
|
||||
export type UmbModalRoute<UmbModalTokenResult> = {
|
||||
// TODO: Consider accepting the Token as a generic:
|
||||
export type UmbModalRoute<UmbModalTokenData extends object, UmbModalTokenResult> = {
|
||||
path: string;
|
||||
onSetup: (routeInfo: any) => void;
|
||||
onSubmit: (data: UmbModalTokenResult) => void;
|
||||
onSetup: (routingInfo: IRoutingInfo) => UmbModalTokenData | false;
|
||||
onSubmit: (data: UmbModalTokenResult) => void | PromiseLike<void>;
|
||||
onReject: () => void;
|
||||
};
|
||||
|
||||
export type UmbModalRouteBuilder = (params: { [key: string]: string | number }) => string;
|
||||
|
||||
export class UmbRouteContext {
|
||||
#host: UmbControllerHostInterface;
|
||||
#modalContext?: typeof UMB_MODAL_CONTEXT_TOKEN.TYPE;
|
||||
#contextRoutes: IRoute[] = [];
|
||||
|
||||
constructor(host: UmbControllerHostInterface, private _onGotModals: (contextRoutes: any) => void) {
|
||||
@@ -25,28 +26,54 @@ export class UmbRouteContext {
|
||||
/*new UmbContextConsumerController(host, UMB_ROUTE_CONTEXT_TOKEN, (context) => {
|
||||
console.log('got a parent', this === context, this, context);
|
||||
});*/
|
||||
new UmbContextConsumerController(host, UMB_MODAL_CONTEXT_TOKEN, (context) => {
|
||||
this.#modalContext = context;
|
||||
});
|
||||
|
||||
// Consider using this event, to stay up to date with current full-URL. which is necessary for Modal opening.
|
||||
// window.addEventListener('navigationsuccess', this._onNavigationChanged);
|
||||
}
|
||||
|
||||
public registerModal<T extends UmbModalToken = UmbModalToken, R = GetResultType<T>>(
|
||||
modalAlias: T,
|
||||
options: UmbModalRoute<R>
|
||||
) {
|
||||
console.log('registerModalRoute', modalAlias.toString(), options);
|
||||
|
||||
public registerModal<D extends object = object, R = unknown>(
|
||||
modalAlias: UmbModalToken<D, R> | string,
|
||||
options: UmbModalRoute<D, R>
|
||||
): UmbModalRouteBuilder {
|
||||
const localPath = `modal/${modalAlias.toString()}/${options.path}`;
|
||||
this.#contextRoutes.push({
|
||||
path: options.path,
|
||||
path: localPath,
|
||||
pathMatch: 'suffix',
|
||||
component: EmptyDiv,
|
||||
setup: (component, info) => {
|
||||
console.log('modal open?', info);
|
||||
options.onSetup(info);
|
||||
const modalData = options.onSetup(info);
|
||||
if (modalData && this.#modalContext) {
|
||||
const modalHandler = this.#modalContext.open<D, R>(modalAlias, modalData);
|
||||
modalHandler.onSubmit().then(
|
||||
() => this._removeModalPath(info),
|
||||
() => this._removeModalPath(info)
|
||||
);
|
||||
modalHandler.onSubmit().then(options.onSubmit, options.onReject);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
//TODO: move to a method:
|
||||
this._onGotModals(this.#contextRoutes);
|
||||
|
||||
return (params: { [key: string]: string | number }) => {
|
||||
const localRoutePath = stripSlash(
|
||||
localPath.replace(PARAM_IDENTIFIER, (substring: string, ...args: string[]) => {
|
||||
return params[args[0]];
|
||||
//return `([^\/]+)`;
|
||||
})
|
||||
);
|
||||
const baseRoutePath = window.location.href;
|
||||
return (baseRoutePath.endsWith('/') ? baseRoutePath : baseRoutePath + '/') + localRoutePath;
|
||||
};
|
||||
}
|
||||
|
||||
private _removeModalPath(info: IRoutingInfo) {
|
||||
window.history.pushState({}, '', window.location.href.split(info.match.fragments.consumed)[0]);
|
||||
console.log('ask to remove path', info.match.fragments.consumed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ export class UmbRouterSlotElement extends UmbLitElement {
|
||||
|
||||
#routeContext = new UmbRouteContext(this, (contextRoutes) => {
|
||||
(this.#modalRouter as any).routes = contextRoutes;
|
||||
console.log(this.absoluteRouterPath, this.#router, this.#modalRouter, 'Router got context routes', contextRoutes);
|
||||
// Force a render?
|
||||
this.#modalRouter.render();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user