diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/views/design/workspace-view-document-type-design.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/views/design/workspace-view-document-type-design.element.ts index 2b50b0310e..a27bfb57fb 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/views/design/workspace-view-document-type-design.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/views/design/workspace-view-document-type-design.element.ts @@ -123,8 +123,9 @@ export class UmbWorkspaceViewDocumentTypeDesignElement extends UmbLitElement {
- - + + +
diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/views/templates/workspace-view-document-type-templates.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/views/templates/workspace-view-document-type-templates.element.ts index 99fbdacaa7..cc89db1358 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/views/templates/workspace-view-document-type-templates.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/documents/document-types/workspace/views/templates/workspace-view-document-type-templates.element.ts @@ -1,10 +1,9 @@ import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { customElement, property, state } from 'lit/decorators.js'; +import { customElement, state } from 'lit/decorators.js'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; import { DocumentTypeResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbWorkspaceDocumentTypeContext } from '../../document-type-workspace.context'; - import '../../../../../shared/property-creator/property-creator.element.ts'; @customElement('umb-workspace-view-document-type-templates') @@ -37,24 +36,13 @@ export class UmbWorkspaceViewDocumentTypeTemplatesElement extends UmbLitElement `, ]; - @property() - defaultTemplateKey?: string = '123'; - @state() _documentType?: DocumentTypeResponseModel; - @state() - _templates = [ - { key: '123', name: 'Blog Post Page' }, - { key: '456', name: 'Blog Entry Page' }, - ]; - private _workspaceContext?: UmbWorkspaceDocumentTypeContext; constructor() { super(); - - // TODO: Figure out if this is the best way to consume the context or if it can be strongly typed with an UmbContextToken this.consumeContext('umbWorkspaceContext', (documentTypeContext) => { this._workspaceContext = documentTypeContext; this._observeDocumentType(); @@ -69,13 +57,14 @@ export class UmbWorkspaceViewDocumentTypeTemplatesElement extends UmbLitElement }); } - #changeDefaultTemplate(e: CustomEvent) { - //this.defaultTemplateKey = (e.target as UmbTemplateCardElement).value as string; - console.log('default template key', this.defaultTemplateKey); + async #changeDefaultKey(e: CustomEvent) { + // save new default key + console.log('workspace: default template key', e); } - #removeTemplate(key: string) { - console.log('remove template', key); + #changeAllowedKeys(e: CustomEvent) { + // save new allowed keys + console.log('workspace: allowed templates changed', e); } render() { @@ -83,7 +72,11 @@ export class UmbWorkspaceViewDocumentTypeTemplatesElement extends UmbLitElement
Choose which templates editors are allowed to use on content of this type
- +
`; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-template-picker/input-template-picker.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-template-picker/input-template-picker.element.ts index 8311ae6099..828f13bfe1 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-template-picker/input-template-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/input-template-picker/input-template-picker.element.ts @@ -1,15 +1,14 @@ import { css, html } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { customElement, property, queryAll, state } from 'lit/decorators.js'; -import { repeat } from 'lit/directives/repeat.js'; +import { customElement, property, state } from 'lit/decorators.js'; import { FormControlMixin } from '@umbraco-ui/uui-base/lib/mixins'; import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; import { UmbModalContext, UMB_MODAL_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/modal'; import { UmbLitElement } from '@umbraco-cms/internal/lit-element'; - -import { TemplateResource } from '@umbraco-cms/backoffice/backend-api'; -import { UMB_CONFIRM_MODAL_TOKEN } from '../../modals/confirm'; +import { TemplateResource, TemplateResponseModel } from '@umbraco-cms/backoffice/backend-api'; import { UmbTemplateCardElement } from '../template-card/template-card.element'; +import { UMB_TEMPLATE_PICKER_MODAL_TOKEN } from '../../modals/template-picker'; +import { UMB_TEMPLATE_MODAL_TOKEN } from '../../modals/template'; @customElement('umb-input-template-picker') export class UmbInputTemplatePickerElement extends FormControlMixin(UmbLitElement) { @@ -31,19 +30,6 @@ export class UmbInputTemplatePickerElement extends FormControlMixin(UmbLitElemen min-width: 180px; min-height: 150px; } - - .fade-in { - animation: fadeIn 1s; - } - - @keyframes fadeIn { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } - } `, ]; /** @@ -82,30 +68,43 @@ export class UmbInputTemplatePickerElement extends FormControlMixin(UmbLitElemen @property({ type: String, attribute: 'min-message' }) maxMessage = 'This field exceeds the allowed amount of items'; - @state() - private _items: Array = [ - { key: '2bf464b6-3aca-4388-b043-4eb439cc2643', name: 'Doc 1', default: false }, - { key: '9a84c0b3-03b4-4dd4-84ac-706740ac0f71', name: 'Test', default: true }, - ]; + _allowedKeys: Array = []; + @property({ type: Array }) + public get allowedKeys() { + return this._allowedKeys; + } + public set allowedKeys(newKeys: Array) { + //this.#observePickedTemplates(); + this._allowedKeys = newKeys; + } + + _defaultKey = ''; + @property({ type: String }) + public get defaultKey(): string { + return this._defaultKey; + } + public set defaultKey(newKey: string) { + this._defaultKey = newKey; + super.value = newKey; + } private _modalContext?: UmbModalContext; - //private _documentStore?: UmbDocumentTreeStore; - //private _pickedItemsObserver?: UmbObserverController; + //private _templateStore?: UmbTemplateTreeStore; + //private _pickedItemsObserver?: UmbObserverController; + + @state() + _templates: TemplateResponseModel[] = []; + + public get templates(): TemplateResponseModel[] { + return this._templates; + } + public set templates(newTemplates: TemplateResponseModel[]) { + this._templates = newTemplates; + this.allowedKeys = newTemplates.map((template) => template.key ?? ''); + } constructor() { super(); - - this.addValidator( - 'rangeUnderflow', - () => this.minMessage, - () => !!this.min && this._items.length < this.min - ); - this.addValidator( - 'rangeOverflow', - () => this.maxMessage, - () => !!this.max && this._items.length > this.max - ); - this.consumeContext(UMB_MODAL_CONTEXT_TOKEN, (instance) => { this._modalContext = instance; }); @@ -113,79 +112,83 @@ export class UmbInputTemplatePickerElement extends FormControlMixin(UmbLitElemen connectedCallback(): void { super.connectedCallback(); - this._items = this._items.sort((a, b) => b.default - a.default); - this.#setup(); + this.allowedKeys.forEach((key) => this.#setup(key)); } - async #setup() { - const templates = await tryExecuteAndNotify(this, TemplateResource.getTreeTemplateRoot({ skip: 0, take: 9999 })); - console.log(templates); + async #setup(templateKey: string) { + const { data } = await tryExecuteAndNotify(this, TemplateResource.getTemplateByKey({ key: templateKey })); + if (!data) return; + this.templates = [...this.templates, data]; } protected getFormElement() { return undefined; } - #openTemplatePickerModal() { - console.log('template picker modal'); - } - - #changeSelected() { - console.log('selected'); - } - - /** Clicking the template card buttons */ - #changeDefault(e: CustomEvent) { - const key = (e.target as UmbTemplateCardElement).value; + e.stopPropagation(); + const newKey = (e.target as UmbTemplateCardElement).value as string; + this.defaultKey = newKey; + this.dispatchEvent(new CustomEvent('change-default', { bubbles: true, composed: true })); + } - const oldDefault = this._items.find((x) => x.default === true); - const newDefault = this._items.find((x) => x.key === key); - - const items = this._items.map((item) => { - if (item.default === true) return { ...newDefault, default: true }; - if (item.key === key) return { ...oldDefault, default: false }; - return item; + #openPicker() { + //TODO: Tree-picker modal? + const modalHandler = this._modalContext?.open(UMB_TEMPLATE_PICKER_MODAL_TOKEN, { + multiple: true, + selection: [...this.allowedKeys], }); - this._items = items; - } - - #openTemplate(e: CustomEvent) { - const key = (e.target as UmbTemplateCardElement).value; - console.log('open', key); + modalHandler?.onSubmit().then((data) => { + console.log(data.selection); + this.dispatchEvent(new CustomEvent('change-allowed', { bubbles: true, composed: true })); + }); } #removeTemplate(key: string) { - console.log('remove', key); + console.log('picker: remove', key); + const templateIndex = this.templates.findIndex((x) => x.key === key); + this.templates.splice(templateIndex, 1); + this.templates = [...this._templates]; } render() { return html` - ${repeat( - this._items, - (template) => template.default, - (template, index) => html`
+ ${this.templates.map( + (template) => html` + ?default="${template.key === this.defaultKey}"> -
` + ` )} - Add + Add `; } + + #openTemplate(e: CustomEvent) { + const key = (e.target as UmbTemplateCardElement).value; + + const modalHandler = this._modalContext?.open(UMB_TEMPLATE_MODAL_TOKEN, { + multiple: true, + selection: [...this.allowedKeys], + }); + + modalHandler?.onSubmit().then((res) => { + console.log('save template'); + }); + } } export default UmbInputTemplatePickerElement; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/template-card/template-card.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/template-card/template-card.element.ts index c971f22dbb..50f8cc5484 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/template-card/template-card.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/template-card/template-card.element.ts @@ -139,7 +139,7 @@ export class UmbTemplateCardElement extends FormControlMixin(UmbLitElement) { e.preventDefault(); e.stopPropagation(); //this.selected = true; - this.dispatchEvent(new CustomEvent('default-change', { bubbles: true, composed: true })); + this.dispatchEvent(new CustomEvent('change-default', { bubbles: true, composed: true })); } #openTemplate(e: KeyboardEvent) { e.preventDefault(); @@ -149,7 +149,6 @@ export class UmbTemplateCardElement extends FormControlMixin(UmbLitElement) { render() { return html`
-
`; } } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.element.ts index bbe2cf2a8e..f1fe4b7ff2 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/components/workspace/workspace-layout/workspace-layout.element.ts @@ -40,9 +40,9 @@ export class UmbWorkspaceLayout extends UmbLitElement { } #router-slot { - display:flex; - flex-direction:column; - height:100%; + display: flex; + flex-direction: column; + height: 100%; } uui-input { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/manifests.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/manifests.ts index ac7dfa75d5..78fa2bd2b7 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/manifests.ts @@ -31,6 +31,18 @@ const modals: Array = [ name: 'Section Picker Modal', loader: () => import('./section-picker/section-picker-modal.element'), }, + { + type: 'modal', + alias: 'Umb.Modal.TemplatePicker', + name: 'Template Picker Modal', + loader: () => import('./template-picker/template-picker-modal.element'), + }, + { + type: 'modal', + alias: 'Umb.Modal.Template', + name: 'Template Modal', + loader: () => import('./template/template-modal.element'), + }, ]; export const manifests = [...modals]; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/template-picker/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/template-picker/index.ts new file mode 100644 index 0000000000..f8f2560e3e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/template-picker/index.ts @@ -0,0 +1,18 @@ +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; + +export interface UmbTemplatePickerModalData { + multiple: boolean; + selection: string[]; +} + +export interface UmbTemplatePickerModalResult { + selection: string[] | undefined; +} + +export const UMB_TEMPLATE_PICKER_MODAL_TOKEN = new UmbModalToken< + UmbTemplatePickerModalData, + UmbTemplatePickerModalResult +>('Umb.Modal.TemplatePicker', { + type: 'sidebar', + size: 'small', +}); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/template-picker/template-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/template-picker/template-picker-modal.element.ts new file mode 100644 index 0000000000..503b3113a8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/template-picker/template-picker-modal.element.ts @@ -0,0 +1,104 @@ +import { css, html } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, state } from 'lit/decorators.js'; +import { UmbModalBaseElement } from '@umbraco-cms/internal/modal'; +import { UmbTreeElement } from '../../components/tree/tree.element'; +import { UmbTemplatePickerModalData, UmbTemplatePickerModalResult } from '.'; + +//TODO: make a default tree-picker that can be used across multiple pickers +// TODO: make use of UmbPickerLayoutBase +@customElement('umb-template-picker-modal') +export class UmbTemplatePickerModalElement extends UmbModalBaseElement< + UmbTemplatePickerModalData, + UmbTemplatePickerModalResult +> { + static styles = [ + UUITextStyles, + css` + h3 { + margin-left: var(--uui-size-space-5); + margin-right: var(--uui-size-space-5); + } + + uui-input { + width: 100%; + } + + hr { + border: none; + border-bottom: 1px solid var(--uui-color-divider); + margin: 16px 0; + } + + #content-list { + display: flex; + flex-direction: column; + gap: var(--uui-size-space-3); + } + + .content-item { + cursor: pointer; + } + + .content-item.selected { + background-color: var(--uui-color-selected); + color: var(--uui-color-selected-contrast); + } + `, + ]; + + @state() + _selection: Array = []; + + @state() + _multiple = true; + + connectedCallback() { + super.connectedCallback(); + this._selection = this.data?.selection ?? []; + this._multiple = this.data?.multiple ?? true; + } + + private _handleSelectionChange(e: CustomEvent) { + e.stopPropagation(); + const element = e.target as UmbTreeElement; + //TODO: Should multiple property be implemented here or be passed down into umb-tree? + this._selection = this._multiple ? element.selection : [element.selection[element.selection.length - 1]]; + } + + private _submit() { + this.modalHandler?.submit({ selection: this._selection }); + } + + private _close() { + this.modalHandler?.reject(); + } + + render() { + return html` + + + +
+ +
+
+ + +
+
+ `; + } +} + +export default UmbTemplatePickerModalElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-template-picker-modal': UmbTemplatePickerModalElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/template/index.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/template/index.ts new file mode 100644 index 0000000000..28f1568e30 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/template/index.ts @@ -0,0 +1,18 @@ +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; + +export interface UmbTemplateModalData { + multiple: boolean; + selection: string[]; +} + +export interface UmbTemplateModalResult { + selection: string[] | undefined; +} + +export const UMB_TEMPLATE_MODAL_TOKEN = new UmbModalToken( + 'Umb.Modal.Template', + { + type: 'sidebar', + size: 'large', + } +); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/template/template-modal.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/template/template-modal.element.ts new file mode 100644 index 0000000000..89a6f66061 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/shared/modals/template/template-modal.element.ts @@ -0,0 +1,97 @@ +import { css, html } from 'lit'; +import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; +import { customElement, state } from 'lit/decorators.js'; +import { UmbModalBaseElement } from '@umbraco-cms/internal/modal'; +import { UmbTreeElement } from '../../components/tree/tree.element'; +import { UmbTemplateModalData, UmbTemplateModalResult } from '.'; + +//TODO: make a default tree-picker that can be used across multiple pickers +// TODO: make use of UmbPickerLayoutBase +@customElement('umb-template-modal') +export class UmbTemplateModalElement extends UmbModalBaseElement { + static styles = [ + UUITextStyles, + css` + h3 { + margin-left: var(--uui-size-space-5); + margin-right: var(--uui-size-space-5); + } + + uui-input { + width: 100%; + } + + hr { + border: none; + border-bottom: 1px solid var(--uui-color-divider); + margin: 16px 0; + } + + #content-list { + display: flex; + flex-direction: column; + gap: var(--uui-size-space-3); + } + + .content-item { + cursor: pointer; + } + + .content-item.selected { + background-color: var(--uui-color-selected); + color: var(--uui-color-selected-contrast); + } + `, + ]; + + @state() + _selection: Array = []; + + @state() + _multiple = true; + + connectedCallback() { + super.connectedCallback(); + this._selection = this.data?.selection ?? []; + this._multiple = this.data?.multiple ?? true; + } + + private _handleSelectionChange(e: CustomEvent) { + e.stopPropagation(); + const element = e.target as UmbTreeElement; + //TODO: Should multiple property be implemented here or be passed down into umb-tree? + this._selection = this._multiple ? element.selection : [element.selection[element.selection.length - 1]]; + } + + private _submit() { + this.modalHandler?.submit({ selection: this._selection }); + } + + private _close() { + this.modalHandler?.reject(); + } + + render() { + return html` + + + +
+ Code editor? +
+
+ + +
+
+ `; + } +} + +export default UmbTemplateModalElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-template-modal': UmbTemplateModalElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/data/document-type.data.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/data/document-type.data.ts index b02af718db..1f418b6b0c 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/data/document-type.data.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/data/document-type.data.ts @@ -890,8 +890,12 @@ export const data: Array = [ }, }, { - allowedTemplateKeys: [], - defaultTemplateKey: null, + allowedTemplateKeys: [ + '2bf464b6-3aca-4388-b043-4eb439cc2643', + '9a84c0b3-03b4-4dd4-84ac-706740ac0f71', + '9a84c0b3-03b4-4dd4-84ac-706740ac0f72', + ], + defaultTemplateKey: '2bf464b6-3aca-4388-b043-4eb439cc2643', key: 'simple-document-type-key', alias: 'simpleDocumentType', name: 'Simple Document Type',