diff --git a/src/Umbraco.Web.UI.Client/examples/ufm-custom-component/index.ts b/src/Umbraco.Web.UI.Client/examples/ufm-custom-component/index.ts index af49cdc0f3..67d3281921 100644 --- a/src/Umbraco.Web.UI.Client/examples/ufm-custom-component/index.ts +++ b/src/Umbraco.Web.UI.Client/examples/ufm-custom-component/index.ts @@ -8,6 +8,7 @@ export const manifests: Array = [ api: () => import('./custom-ufm-component.js'), meta: { marker: '%', + alias: 'myCustomComponent', }, }, ]; diff --git a/src/Umbraco.Web.UI.Client/examples/validation-context/index.ts b/src/Umbraco.Web.UI.Client/examples/validation-context/index.ts new file mode 100644 index 0000000000..b64c960171 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/validation-context/index.ts @@ -0,0 +1,23 @@ +import type { ManifestDashboard } from '@umbraco-cms/backoffice/dashboard'; + +const dashboard : ManifestDashboard = { + type: 'dashboard', + alias: 'Demo.Dashboard', + name: 'Demo Dashboard Validation Context', + weight: 1000, + element: () => import('./validation-context-dashboard.js'), + meta: { + label: 'Validation Context Demo', + pathname: 'demo' + }, + conditions : [ + { + alias : "Umb.Condition.SectionAlias", + match : "Umb.Section.Content" + } + ] +} + +export const manifests = [ + dashboard +]; diff --git a/src/Umbraco.Web.UI.Client/examples/validation-context/validation-context-dashboard.ts b/src/Umbraco.Web.UI.Client/examples/validation-context/validation-context-dashboard.ts new file mode 100644 index 0000000000..8fd8587c34 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/validation-context/validation-context-dashboard.ts @@ -0,0 +1,219 @@ +import { html, customElement, css, state, when } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UMB_VALIDATION_CONTEXT, umbBindToValidation, UmbValidationContext } from '@umbraco-cms/backoffice/validation'; + +@customElement('umb-example-validation-context-dashboard') +export class UmbExampleValidationContextDashboard extends UmbLitElement { + + readonly validation = new UmbValidationContext(this); + + @state() + name = ''; + + @state() + email = ''; + + @state() + city = ''; + + @state() + country = ''; + + @state() + messages? : any[] + + @state() + totalErrorCount = 0; + + @state() + tab1ErrorCount = 0; + + @state() + tab2ErrorCount = 0; + + @state() + tab = "1"; + + constructor() { + super(); + + this.consumeContext(UMB_VALIDATION_CONTEXT,(validationContext)=>{ + + this.observe(validationContext.messages.messages,(messages)=>{ + this.messages = messages; + },'observeValidationMessages') + + // Observe all errors + this.validation.messages.messagesOfPathAndDescendant('$.form').subscribe((value)=>{ + this.totalErrorCount = [...new Set(value.map(x=>x.path))].length; + }); + + // Observe errors for tab1, note that we only use part of the full JSONPath + this.validation.messages.messagesOfPathAndDescendant('$.form.tab1').subscribe((value)=>{ + this.tab1ErrorCount = [...new Set(value.map(x=>x.path))].length; + }); + + // Observe errors for tab2, note that we only use part of the full JSONPath + this.validation.messages.messagesOfPathAndDescendant('$.form.tab2').subscribe((value)=>{ + this.tab2ErrorCount = [...new Set(value.map(x=>x.path))].length; + }); + + }); + } + + #onTabChange(e:Event) { + this.tab = (e.target as HTMLElement).getAttribute('data-tab') as string; + } + + #handleSave() { + + // fake server validation-errors for all fields + if(this.name == '') + this.validation.messages.addMessage('server','$.form.tab1.name','Name server-error message','4875e113-cd0c-4c57-ac92-53d677ba31ec'); + if(this.email == '') + this.validation.messages.addMessage('server','$.form.tab1.email','Email server-error message','a47e287b-4ce6-4e8b-8e05-614e7cec1a2a'); + if(this.city == '') + this.validation.messages.addMessage('server','$.form.tab2.city','City server-error message','8dfc2f15-fb9a-463b-bcec-2c5d3ba2d07d'); + if(this.country == '') + this.validation.messages.addMessage('server','$.form.tab2.country','Country server-error message','d98624f6-82a2-4e94-822a-776b44b01495'); + } + + override render() { + return html` + + This is a demo of how the Validation Context can be used to validate a form with multiple steps. Start typing in the form or press Save to trigger validation. +
+ Total errors: ${this.totalErrorCount} +
+ + + Tab 1 + ${when(this.tab1ErrorCount,()=>html` + ${this.tab1ErrorCount} + `)} + + + Tab 2 + ${when(this.tab2ErrorCount,()=>html` + ${this.tab2ErrorCount} + `)} + + + + ${when(this.tab=='1',()=>html` + ${this.#renderTab1()} + `)} + ${when(this.tab=='2',()=>html` + ${this.#renderTab2()} + `)} + + Save +
+

Validation Context Messages

+
${JSON.stringify(this.messages ?? [],null,3)}
+
+ ` + } + + #renderTab1() { + return html` + +
+
+ + + this.name = (e.target as HTMLInputElement).value} + ${umbBindToValidation(this,'$.form.tab1.name',this.name)} + required> + +
+ + + this.email = (e.target as HTMLInputElement).value} + ${umbBindToValidation(this,'$.form.tab1.email',this.email)} + required> + +
+
+ ` + } + + #renderTab2() { + return html` + +
+
+ + + this.city = (e.target as HTMLInputElement).value} + ${umbBindToValidation(this,'$.form.tab2.city',this.city)} + required> + +
+ + + this.country = (e.target as HTMLInputElement).value} + ${umbBindToValidation(this,'$.form.tab2.country',this.country)} + required> + +
+
+ ` + } + + + + static override styles = [css` + + uui-badge { + top:0; + right:0; + font-size:10px; + min-width:17px; + min-height:17px; + + } + + label { + display:block; + } + + uui-box { + margin:20px; + } + + uui-button { + margin-top:1rem; + } + + pre { + text-align:left; + padding:10px; + border:1px dotted #6f6f6f; + background: #f2f2f2; + font-size: 11px; + line-height: 1.3em; + } + + `] +} + +export default UmbExampleValidationContextDashboard; + +declare global { + interface HTMLElementTagNameMap { + 'umb-example-validation-context-dashboard': UmbExampleValidationContextDashboard; + } +} diff --git a/src/Umbraco.Web.UI.Client/examples/workspace-context-counter/index.ts b/src/Umbraco.Web.UI.Client/examples/workspace-context-counter/index.ts index 2bd252893b..edc248b4a9 100644 --- a/src/Umbraco.Web.UI.Client/examples/workspace-context-counter/index.ts +++ b/src/Umbraco.Web.UI.Client/examples/workspace-context-counter/index.ts @@ -1,3 +1,5 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; + export const manifests: Array = [ { type: 'workspaceContext', @@ -6,7 +8,7 @@ export const manifests: Array = [ api: () => import('./counter-workspace-context.js'), conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: 'Umb.Workspace.Document', }, ], @@ -25,7 +27,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: 'Umb.Workspace.Document', }, ], @@ -43,7 +45,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: 'Umb.Workspace.Document', }, ], diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 0242e359b1..168c6ae73d 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -41,6 +41,7 @@ "./document": "./dist-cms/packages/documents/documents/index.js", "./entity-action": "./dist-cms/packages/core/entity-action/index.js", "./entity-bulk-action": "./dist-cms/packages/core/entity-bulk-action/index.js", + "./entity-create-option-action": "./dist-cms/packages/core/entity-create-option-action/index.js", "./entity": "./dist-cms/packages/core/entity/index.js", "./event": "./dist-cms/packages/core/event/index.js", "./extension-registry": "./dist-cms/packages/core/extension-registry/index.js", diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/ar.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/ar.ts index 5f57427859..8211f08edc 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/ar.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/ar.ts @@ -481,7 +481,7 @@ export default { 'قد يكون تغيير الثقافة للغة عملية مكلفة وستؤدي إلى إعادة بناء ذاكرة التخزين المؤقت للمحتوى والفهارس', lastEdited: 'آخر تحرير', link: 'رابط', - linkinternal: 'رابط داخلي:', + linkinternal: 'رابط داخلي', linklocaltip: 'عند استخدام الروابط المحلية، أدخل "#" أمام الرابط', linknewwindow: 'فتح في نافذة جديدة؟', macroDoesNotHaveProperties: 'هذا الماكرو لا يحتوي على خصائص يمكنك تحريرها', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/bs.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/bs.ts index 159d15d541..eebc3ee1e5 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/bs.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/bs.ts @@ -482,7 +482,7 @@ export default { 'Promjena kulture jezika može biti skupa operacija i rezultirat će\n u kešu sadržaja i indeksima koji se rekonstruišu\n ', lastEdited: 'Posljednji put uređeno', link: 'Link', - linkinternal: 'Interni link:', + linkinternal: 'Interni link', linklocaltip: 'Kada koristite lokalne veze, umetnite "#" ispred linka', linknewwindow: 'Otvori u novom prozoru?', macroDoesNotHaveProperties: 'Ovaj makro ne sadrži svojstva koja možete uređivati', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/cs-cz.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/cs-cz.ts index 0f47ba053f..90f89601a6 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/cs-cz.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/cs-cz.ts @@ -429,7 +429,7 @@ export default { 'Změna kultury jazyka může být náročná operace a bude mít za následek opětovné sestavení mezipaměti obsahu a indexů', lastEdited: 'Naposledy editováno', link: 'Odkaz', - linkinternal: 'Místní odkaz:', + linkinternal: 'Místní odkaz', linklocaltip: 'Při používání místních odkazů vložte znak "#" před odkaz', linknewwindow: 'Otevřít v novém okně?', macroDoesNotHaveProperties: 'Toto makro nemá žádné vlastnosti, které by bylo možno editovat', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/cy-gb.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/cy-gb.ts index 01ca531714..a3b0dc27b5 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/cy-gb.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/cy-gb.ts @@ -504,7 +504,7 @@ export default { "Gall newid y diwylliant ar gyfer iaith fod yn weithrediad drud a bydd yn arwain at ailadeiladu'r storfa cynnwys a'r mynegeion", lastEdited: 'Golygwyd ddiwethaf', link: 'Dolen', - linkinternal: 'Dolen fewnol:', + linkinternal: 'Dolen fewnol', linklocaltip: 'Wrth ddefnyddio dolenni leol, defnyddiwch "#" o flaen y ddolen', linknewwindow: 'Agor mewn ffenestr newydd?', macroDoesNotHaveProperties: "Nid yw'r macro yma yn cynnwys unrhyw briodweddau gallwch chi olygu", diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts index b95bdce162..224cf316de 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/da-dk.ts @@ -499,7 +499,7 @@ export default { 'Ændring af kulturen for et sprog kan forsage en krævende opration og vil\n resultere i indholds cache og indeksering vil blive genlavet\n ', lastEdited: 'Sidst redigeret', link: 'Link', - linkinternal: 'Internt link:', + linkinternal: 'Internt link', linklocaltip: 'Ved lokalt link, indsæt da en "#" foran linket', linknewwindow: 'Åben i nyt vindue?', macroDoesNotHaveProperties: 'Denne makro har ingen egenskaber du kan redigere', @@ -783,6 +783,7 @@ export default { email: 'E-mail', error: 'Fejl', field: 'Felt', + fieldFor: 'Felt for %0%', findDocument: 'Find', first: 'Første', focalPoint: 'Fokuspunkt', @@ -900,6 +901,7 @@ export default { avatar: 'Avatar til', header: 'Overskrift', systemField: 'system felt', + readOnly: 'Skrivebeskyttet', restore: 'Genskab', generic: 'Generic', media: 'Media', @@ -2362,7 +2364,8 @@ export default { allowBlockInAreas: 'Allow in areas', allowBlockInAreasHelp: 'Make this block available by default within the areas of other Blocks (unless explicit permissions are set for these areas).', - createThisFor: 'Opret %0% for %1%', + createThisFor: (name: string, variantName: string) => + variantName ? `Opret ${name} for ${variantName}` : `Create ${name}`, insertBlock: 'Indsæt Block', labelInlineMode: 'Indsæt på linje med tekst', }, diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/de-de.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/de-de.ts index 66000861c1..7733d6fa34 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/de-de.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/de-de.ts @@ -503,7 +503,7 @@ export default { '\n Die Kultur-Variante einer Sprache zu ändern ist möglicherweise eine aufwendige Operation und führt zum Erneuern von Inhalts-Zwischenspeicher und Such-Index.\n ', lastEdited: 'Zuletzt bearbeitet', link: 'Verknüpfung', - linkinternal: 'Anker:', + linkinternal: 'Internen Link', linklocaltip: 'Wenn lokale Links verwendet werden, füge ein "#" vor den Link ein', linknewwindow: 'In einem neuen Fenster öffnen?', macroDoesNotHaveProperties: 'Dieses Makro enthält keine einstellbaren Eigenschaften.', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts index ccc472ee6b..6f53119e14 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en-us.ts @@ -521,7 +521,7 @@ export default { 'Changing the culture for a language may be an expensive operation and will result\n in the content cache and indexes being rebuilt\n ', lastEdited: 'Last Edited', link: 'Link', - linkinternal: 'Internal link:', + linkinternal: 'Internal link', linklocaltip: 'When using local links, insert "#" in front of link', linknewwindow: 'Open in new window?', macroDoesNotHaveProperties: 'This macro does not contain any properties you can edit', @@ -553,7 +553,7 @@ export default { relateToOriginalLabel: 'Relate to original', includeDescendants: 'Include descendants', theFriendliestCommunity: 'The friendliest community', - linkToPage: 'Link to page', + linkToPage: 'Link to document', openInNewWindow: 'Opens the linked document in a new window or tab', linkToMedia: 'Link to media', selectContentStartNode: 'Select content start node', @@ -566,8 +566,8 @@ export default { selectContent: 'Select content', selectContentType: 'Select content type', selectMediaStartNode: 'Select media start node', - selectMember: 'Select member', - selectMembers: 'Select members', + selectMember: 'Choose member', + selectMembers: 'Choose members', selectMemberGroup: 'Select member group', selectMemberType: 'Select member type', selectNode: 'Select node', @@ -803,6 +803,7 @@ export default { email: 'Email', error: 'Error', field: 'Field', + fieldFor: 'Field for %0%', findDocument: 'Find', first: 'First', focalPoint: 'Focal point', @@ -935,6 +936,7 @@ export default { skipToMenu: 'Skip to menu', skipToContent: 'Skip to content', restore: 'Restore', + readOnly: 'Read-only', newVersionAvailable: 'New version available', }, colors: { @@ -2515,7 +2517,8 @@ export default { configureArea: 'Configure area', deleteArea: 'Delete area', addColumnSpanOption: 'Add spanning %0% columns option', - createThisFor: 'Create %0% for %1%', + createThisFor: (name: string, variantName: string) => + variantName ? `Create ${name} for ${variantName}` : `Create ${name}`, insertBlock: 'Insert Block', labelInlineMode: 'Display inline with text', }, diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts index 968a0fe484..27fb01708b 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/en.ts @@ -47,6 +47,7 @@ export default { notify: 'Notifications', protect: 'Public Access', publish: 'Publish', + readOnly: 'Read-only', refreshNode: 'Reload', remove: 'Remove', rename: 'Rename', @@ -519,7 +520,7 @@ export default { 'Changing the culture for a language may be an expensive operation and will result\n in the content cache and indexes being rebuilt\n ', lastEdited: 'Last Edited', link: 'Link', - linkinternal: 'Internal link:', + linkinternal: 'Internal link', linklocaltip: 'When using local links, insert "#" in front of link', linknewwindow: 'Open in new window?', macroDoesNotHaveProperties: 'This macro does not contain any properties you can edit', @@ -551,7 +552,7 @@ export default { relateToOriginalLabel: 'Relate to original', includeDescendants: 'Include descendants', theFriendliestCommunity: 'The friendliest community', - linkToPage: 'Link to page', + linkToPage: 'Link to document', openInNewWindow: 'Opens the linked document in a new window or tab', linkToMedia: 'Link to media', selectContentStartNode: 'Select content start node', @@ -565,8 +566,8 @@ export default { selectContent: 'Select content', selectContentType: 'Select content type', selectMediaStartNode: 'Select media start node', - selectMember: 'Select member', - selectMembers: 'Select members', + selectMember: 'Choose member', + selectMembers: 'Choose members', selectMemberGroup: 'Select member group', selectMemberType: 'Select member type', selectNode: 'Select node', @@ -805,6 +806,7 @@ export default { email: 'Email', error: 'Error', field: 'Field', + fieldFor: 'Field for %0%', findDocument: 'Find', first: 'First', focalPoint: 'Focal point', @@ -2573,7 +2575,8 @@ export default { configureArea: 'Configure area', deleteArea: 'Delete area', addColumnSpanOption: 'Add spanning %0% columns option', - createThisFor: 'Create %0% for %1%', + createThisFor: (name: string, variantName: string) => + variantName ? `Create ${name} for ${variantName}` : `Create ${name}`, insertBlock: 'Insert Block', labelInlineMode: 'Display inline with text', }, @@ -2650,5 +2653,15 @@ export default { extGroup_interactive: 'Interactive elements', extGroup_media: 'Embeds and media', extGroup_structure: 'Content structure', + extGroup_unknown: 'Uncategorized', + toobar_availableItems: 'Available toolbar items', + toobar_availableItemsEmpty: 'There are no toolbar extensions to show', + toolbar_designer: 'Toolbar designer', + toolbar_addRow: 'Add row configuration', + toolbar_addGroup: 'Add group', + toolbar_addItems: 'Add items', + toolbar_removeRow: 'Remove row', + toolbar_removeGroup: 'Remove group', + toolbar_removeItem: 'Remove item', }, } as UmbLocalizationDictionary; diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/fr-fr.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/fr-fr.ts index f465f3c76d..f62092bcea 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/fr-fr.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/fr-fr.ts @@ -442,7 +442,7 @@ export default { "Modifier la culture d'une langue peut être une opération lourde qui aura pour conséquence la réinitialisation de la cache de contenu et des index", lastEdited: 'Dernière modification', link: 'Lien', - linkinternal: 'Lien interne :', + linkinternal: 'Lien interne', linklocaltip: 'Si vous utilisez des ancres, insérez # au début du lien', linknewwindow: 'Ouvrir dans une nouvelle fenêtre?', macroDoesNotHaveProperties: 'Cette macro ne contient aucune propriété éditable', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/he-il.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/he-il.ts index 827b5e8b09..005f465fcc 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/he-il.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/he-il.ts @@ -178,7 +178,7 @@ export default { inserttable: 'הוסף טבלה', lastEdited: 'נערך לאחרונה', link: 'קישור', - linkinternal: 'קישור פנימי:', + linkinternal: 'קישור פנימי', linklocaltip: 'בעת שימוש בקישוריים פנימיים, הוסף "#" בתחילת הקישור', linknewwindow: 'לפתוח בחלון חדש?', macroDoesNotHaveProperties: 'המאקרו לא מכיל מאפיינים שניתן לערוך', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/hr-hr.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/hr-hr.ts index 3519e19060..23ee4b8d21 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/hr-hr.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/hr-hr.ts @@ -484,7 +484,7 @@ export default { 'Promjena kulture jezika može biti skupa operacija i rezultirat će promjenama u predmemoriji sadržaja i indeksima koji se rekonstruiraju\n ', lastEdited: 'Zadnje uređivano', link: 'Link', - linkinternal: 'Interni link:', + linkinternal: 'Interni link', linklocaltip: 'Kada koristite lokalni linkovi, umetnite "#" ispred linka', linknewwindow: 'Otvoriti u novom prozoru?', macroDoesNotHaveProperties: 'Ovaj makro ne sadrži svojstva koja možete uređivati', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/it-it.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/it-it.ts index 9a7dfb9eec..e991e84f29 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/it-it.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/it-it.ts @@ -495,7 +495,7 @@ export default { "La modifica della cultura di una lingua può essere un'operazione costosa e comporterà la ricostruzione della cache dei contenuti e degli indici", lastEdited: 'Ultima modifica', link: 'Link', - linkinternal: 'Link interno:', + linkinternal: 'Link interno', linklocaltip: 'Quando usi il link locale, inserisci # prima del link', linknewwindow: 'Apri in nuova finestra?', macroContainerSettings: 'Impostazioni della macro', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/ja-jp.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/ja-jp.ts index 8f1c6130e6..489fc8c939 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/ja-jp.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/ja-jp.ts @@ -227,7 +227,7 @@ export default { inserttable: '表の挿入', lastEdited: '最近の更新', link: 'リンク', - linkinternal: '内部リンク:', + linkinternal: '内部リンク', linklocaltip: '内部リンクを使うときは、リンクの前に "#" を挿入してください。', linknewwindow: '新規ウィンドウで開きますか?', macroDoesNotHaveProperties: 'このマクロは編集できるプロパティがありません', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/ko-kr.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/ko-kr.ts index 961d322c5a..2b3508ca9b 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/ko-kr.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/ko-kr.ts @@ -178,7 +178,7 @@ export default { inserttable: '테이블 삽입', lastEdited: '마지막 수정', link: '링크', - linkinternal: '내부링크:', + linkinternal: '내부링크', linklocaltip: '내부링크를 사용하실 때 링크앞에 "#"를 넣어주세요', linknewwindow: '새 창으로 여시겠습니까?', macroDoesNotHaveProperties: '이 매크로에는 편집할 수 있는 항목이 포함되어 있지 않습니다.', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/nb-no.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/nb-no.ts index 92e082b4db..f25989e84d 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/nb-no.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/nb-no.ts @@ -222,7 +222,7 @@ export default { inserttable: 'Sett inn tabell', lastEdited: 'Sist redigert', link: 'Lenke', - linkinternal: 'Intern link:', + linkinternal: 'Intern link', linklocaltip: 'Ved lokal link, sett inn "#" foran link', linknewwindow: 'Åpne i nytt vindu?', macroDoesNotHaveProperties: 'Denne makroen har ingen egenskaper du kan endre', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/nl-nl.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/nl-nl.ts index 9833b8905d..5b7a3e7408 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/nl-nl.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/nl-nl.ts @@ -452,7 +452,7 @@ export default { 'De cultuur veranderen voor een taal kan een langdurige operatie zijn en zal ertoe\n leiden dat de inhoudscache en indexen opnieuw worden opgebouwd\n ', lastEdited: 'Laatst aangepast op', link: 'Link', - linkinternal: 'Interne link:', + linkinternal: 'Interne link', linklocaltip: 'Plaats een hekje (“#”) voor voor interne links.', linknewwindow: 'In nieuw venster openen?', macroDoesNotHaveProperties: 'Deze macro heeft geen eigenschappen die u kunt bewerken', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/pl-pl.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/pl-pl.ts index b02b668dcc..cd22efb437 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/pl-pl.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/pl-pl.ts @@ -302,7 +302,7 @@ export default { inserttable: 'Wstaw tabelę', lastEdited: 'Ostatnio edytowane', link: 'Link', - linkinternal: 'Link wewnętrzny:', + linkinternal: 'Link wewnętrzny', linklocaltip: 'Kiedy używasz odnośników lokalnych, wstaw znak "#" na początku linku', linknewwindow: 'Otworzyć w nowym oknie?', macroDoesNotHaveProperties: 'To makro nie posiada żadnych właściwości, które można edytować', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/pt-br.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/pt-br.ts index 5ab8d07bc2..49e374abb5 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/pt-br.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/pt-br.ts @@ -178,7 +178,7 @@ export default { inserttable: 'Inserir tabela', lastEdited: 'Última Edição', link: 'Link', - linkinternal: 'Link interno:', + linkinternal: 'Link interno', linklocaltip: 'Ao usar links locais insira "#" na frente do link', linknewwindow: 'Abrir em nova janela?', macroDoesNotHaveProperties: 'Este macro não contém nenhuma propriedade que possa ser editada', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/ru-ru.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/ru-ru.ts index 7dd7d145de..4bb00f41c4 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/ru-ru.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/ru-ru.ts @@ -359,7 +359,7 @@ export default { inserttable: 'Вставить таблицу', lastEdited: 'Последняя правка', link: 'Ссылка', - linkinternal: 'Внутренняя ссылка:', + linkinternal: 'Внутренняя ссылка', linklocaltip: 'Для того чтобы определить локальную ссылку, используйте "#" первым символом', linknewwindow: 'Открыть в новом окне?', macroDoesNotHaveProperties: 'Этот макрос не имеет редактируемых свойств', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/sv-se.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/sv-se.ts index 12e5c2b75b..95ff3be286 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/sv-se.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/sv-se.ts @@ -372,10 +372,12 @@ export default { inserttable: 'Infoga tabell', lastEdited: 'Senast redigerad', link: 'Länk', - linkinternal: 'Intern länk:', + linkinternal: 'Intern länk', linklocaltip: 'När du använder lokala länkar, lägg till "#" framför länken', linknewwindow: 'Öppna i nytt fönster?', macroDoesNotHaveProperties: 'Detta makro innehåller inga egenskaper som du kan redigera', + selectMember: 'Välj medlem', + selectMembers: 'Välj medlemmar', paste: 'Klistra in', permissionsEdit: 'Redigera rättigheter för', recycleBinDeleting: 'Allt som ligger i papperskorgen tas nu bort. Stäng inte detta fönster förrän detta är klart', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/tr-tr.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/tr-tr.ts index 681a683680..a347f09442 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/tr-tr.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/tr-tr.ts @@ -437,7 +437,7 @@ export default { 'Bir dil için kültürü değiştirmek pahalı bir işlem olabilir ve içerik önbelleğinin ve dizinlerin yeniden oluşturulmasına neden olabilir', lastEdited: 'Son Düzenleme', link: 'Bağlantı', - linkinternal: 'Dahili bağlantı:', + linkinternal: 'Dahili bağlantı', linklocaltip: 'Yerel bağlantıları kullanırken, bağlantının önüne "#" ekleyin', linknewwindow: 'Yeni pencerede açılsın mı?', macroDoesNotHaveProperties: 'Bu makro düzenleyebileceğiniz herhangi bir özellik içermiyor', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/uk-ua.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/uk-ua.ts index 687193843a..d480b68234 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/uk-ua.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/uk-ua.ts @@ -358,7 +358,7 @@ export default { inserttable: 'Вставити таблицю', lastEdited: 'Остання зміна', link: 'Посилання', - linkinternal: 'Внутрішнє посилання:', + linkinternal: 'Внутрішнє посилання', linklocaltip: 'Для визначення локального посилання, використовуйте "#" першим символом', linknewwindow: 'Відкрити у новому вікні?', macroDoesNotHaveProperties: 'Цей макрос не має властивостей, що редагуються.', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/zh-cn.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/zh-cn.ts index 1681a72f11..41809f04d0 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/zh-cn.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/zh-cn.ts @@ -233,7 +233,7 @@ export default { inserttable: '插入表格', lastEdited: '最近编辑', link: '链接', - linkinternal: '内部链接:', + linkinternal: '内部链接', linklocaltip: '本地链接请用“#”号开头', linknewwindow: '在新窗口中打开?', macroDoesNotHaveProperties: '该宏没有可编辑的属性', diff --git a/src/Umbraco.Web.UI.Client/src/assets/lang/zh-tw.ts b/src/Umbraco.Web.UI.Client/src/assets/lang/zh-tw.ts index 6dfd7f5186..3d08d514a5 100644 --- a/src/Umbraco.Web.UI.Client/src/assets/lang/zh-tw.ts +++ b/src/Umbraco.Web.UI.Client/src/assets/lang/zh-tw.ts @@ -232,7 +232,7 @@ export default { inserttable: '插入表格', lastEdited: '最近編輯', link: '連結', - linkinternal: '內部連結:', + linkinternal: '內部連結', linklocaltip: '本地連結請用“#”號開頭', linknewwindow: '在新視窗中打開?', macroDoesNotHaveProperties: '本巨集沒有包含您可以編輯的屬性', diff --git a/src/Umbraco.Web.UI.Client/src/json-schema/all-packages.ts b/src/Umbraco.Web.UI.Client/src/json-schema/all-packages.ts index 11e3b72b14..2f8a997a00 100644 --- a/src/Umbraco.Web.UI.Client/src/json-schema/all-packages.ts +++ b/src/Umbraco.Web.UI.Client/src/json-schema/all-packages.ts @@ -33,6 +33,7 @@ import '@umbraco-cms/backoffice/document-type'; import '@umbraco-cms/backoffice/document'; import '@umbraco-cms/backoffice/entity-action'; import '@umbraco-cms/backoffice/entity-bulk-action'; +import '@umbraco-cms/backoffice/entity-create-option-action'; import '@umbraco-cms/backoffice/entity'; import '@umbraco-cms/backoffice/event'; import '@umbraco-cms/backoffice/extension-registry'; diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extensions-initializer.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extensions-initializer.controller.ts index f4e1b9d4fc..48d5fd8f49 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extensions-initializer.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/controller/base-extensions-initializer.controller.ts @@ -201,6 +201,7 @@ export abstract class UmbBaseExtensionsInitializer< if (!this.#extensionRegistry) return; const oldPermittedExtsLength = this.#exposedPermittedExts?.length ?? 0; + this._extensions.forEach((extension) => extension.destroy()); (this._extensions as unknown) = undefined; (this.#permittedExts as unknown) = undefined; this.#exposedPermittedExts = undefined; diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.test.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.test.ts index caed039f60..5a9cfce9b5 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.test.ts @@ -8,6 +8,7 @@ import type { } from '../types/index.js'; import { UmbExtensionRegistry } from './extension.registry.js'; import { expect } from '@open-wc/testing'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; interface TestManifestWithMeta extends ManifestBase { meta: unknown; @@ -535,7 +536,7 @@ describe('Add Conditions', () => { // Add a condition with a specific config to Section2 const workspaceCondition: WorkspaceAliasConditionConfig = { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: 'Umb.Workspace.Document', }; @@ -543,7 +544,7 @@ describe('Add Conditions', () => { const updatedWorkspaceExt = extensionRegistry.getByAlias('Umb.Test.Section.2') as ManifestWithDynamicConditions; expect(updatedWorkspaceExt.conditions?.length).to.equal(1); - expect(updatedWorkspaceExt.conditions?.[0]?.alias).to.equal('Umb.Condition.WorkspaceAlias'); + expect(updatedWorkspaceExt.conditions?.[0]?.alias).to.equal(UMB_WORKSPACE_CONDITION_ALIAS); }); it('allows an extension to update with multiple conditions', async () => { @@ -555,7 +556,7 @@ describe('Add Conditions', () => { alias: 'Umb.Test.Condition.Valid', }, { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: 'Umb.Workspace.Document', } as WorkspaceAliasConditionConfig, ]; @@ -566,7 +567,7 @@ describe('Add Conditions', () => { expect(extUpdated.conditions?.length).to.equal(3); expect(extUpdated.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Invalid'); expect(extUpdated.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Valid'); - expect(extUpdated.conditions?.[2]?.alias).to.equal('Umb.Condition.WorkspaceAlias'); + expect(extUpdated.conditions?.[2]?.alias).to.equal(UMB_WORKSPACE_CONDITION_ALIAS); }); it('allows conditions to be prepended when an extension is loaded later on', async () => { @@ -575,7 +576,7 @@ describe('Add Conditions', () => { alias: 'Umb.Test.Condition.Invalid', }, { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: 'Umb.Workspace.Document', } as WorkspaceAliasConditionConfig, ]; @@ -606,7 +607,7 @@ describe('Add Conditions', () => { expect(extUpdated.conditions?.length).to.equal(3); expect(extUpdated.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Valid'); expect(extUpdated.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Invalid'); - expect(extUpdated.conditions?.[2]?.alias).to.equal('Umb.Condition.WorkspaceAlias'); + expect(extUpdated.conditions?.[2]?.alias).to.equal(UMB_WORKSPACE_CONDITION_ALIAS); }); /** @@ -622,7 +623,7 @@ describe('Add Conditions', () => { alias: 'Umb.Test.Condition.Invalid', }, { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: 'Umb.Workspace.Document', } as WorkspaceAliasConditionConfig, ]; diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts index 580da1cb1f..0d53be4b26 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/registry/extension.registry.ts @@ -100,6 +100,7 @@ const sortExtensions = (a: ManifestBase, b: ManifestBase): number => (b.weight | export class UmbExtensionRegistry< IncomingManifestTypes extends ManifestBase, + IncomingConditionConfigTypes extends UmbConditionConfigBase = UmbConditionConfigBase, ManifestTypes extends ManifestBase = IncomingManifestTypes | ManifestBase, > { readonly MANIFEST_TYPES: ManifestTypes = undefined as never; @@ -490,7 +491,7 @@ export class UmbExtensionRegistry< * @param {string} alias - The alias of the extension to append the condition to. * @param {UmbConditionConfigBase} newCondition - The condition to append to the extension. */ - appendCondition(alias: string, newCondition: UmbConditionConfigBase) { + appendCondition(alias: string, newCondition: IncomingConditionConfigTypes) { this.appendConditions(alias, [newCondition]); } @@ -499,7 +500,7 @@ export class UmbExtensionRegistry< * @param {string} alias - The alias of the extension to append the condition to * @param {Array} newConditions - An array of conditions to be appended to an extension manifest. */ - appendConditions(alias: string, newConditions: Array) { + appendConditions(alias: string, newConditions: Array) { const existingConditionsToBeAdded = this.#additionalConditions.get(alias); this.#additionalConditions.set( alias, diff --git a/src/Umbraco.Web.UI.Client/src/libs/localization-api/localization.controller.ts b/src/Umbraco.Web.UI.Client/src/libs/localization-api/localization.controller.ts index 587f7b14d8..d855db82a1 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/localization-api/localization.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/localization-api/localization.controller.ts @@ -109,8 +109,9 @@ export class UmbLocalizationController(key: K, ...args: FunctionParams): string { if (!this.#usedKeys.includes(key)) { diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/basic-state.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/basic-state.ts index 7f5f609da4..d37def15d7 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/basic-state.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/basic-state.ts @@ -1,4 +1,4 @@ -import { BehaviorSubject } from '@umbraco-cms/backoffice/external/rxjs'; +import { BehaviorSubject, type Observable } from '@umbraco-cms/backoffice/external/rxjs'; /** * @class UmbBasicState @@ -20,30 +20,31 @@ export class UmbBasicState { * * this.observe(myState, (latestStateValue) => console.log("Value is: ", latestStateValue)); */ - public asObservable(): ReturnType['asObservable']> { + public asObservable(): Observable { return this._subject.asObservable(); } /** - * @property value + * @property {unknown} value - the value of the State. * @description - Holds the current data of this state. + * @returns {unknown} Observable that * @example Example retrieve the current data of a state * const myState = new UmbArrayState('Hello world'); * console.log("Value is: ", myState.value); */ - public get value(): BehaviorSubject['value'] { + public get value(): T { return this.getValue(); } /** * @function getValue - * @returns {T} The current data of this state. + * @returns {unknown} The current data of this state. * @description - Provides the current data of this state. * @example Example retrieve the current data of a state * const myState = new UmbArrayState('Hello world'); * console.log("Value is: ", myState.value); */ - public getValue(): ReturnType['getValue']> { + public getValue(): T { return this._subject.getValue(); } @@ -58,7 +59,7 @@ export class UmbBasicState { /** * @function setValue - * @param {T} data - The next data for this state to hold. + * @param {unknown} data - The next data for this state to hold. * @description - Set the data of this state, if data is different than current this will trigger observations to update. * @example Example change the data of a state * const myState = new UmbArrayState('Good morning'); diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/class-state.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/class-state.ts index 26fc104415..fad45c4175 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/class-state.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/class-state.ts @@ -1,12 +1,9 @@ import type { MappingFunction } from '../types/mapping-function.type.js'; import type { MemoizationFunction } from '../types/memoization-function.type.js'; +import type { UmbClassStateData } from '../utils/class-equal-memoization.function.js'; import { createObservablePart } from '../utils/create-observable-part.function.js'; import { UmbBasicState } from './basic-state.js'; -export interface UmbClassStateData { - equal(otherClass: this | undefined): boolean; -} - /** * @class UmbClassState * @augments {UmbBasicState} @@ -18,10 +15,10 @@ export class UmbClassState extends UmbB } /** - * @function createObservablePart - * @param {(mappable: T) => R} mappingFunction - Method to return the part for this Observable to return. - * @param {(previousResult: R, currentResult: R) => boolean} [memoizationFunction] - Method to Compare if the data has changed. Should return true when data is different. + * @param {(mappable: UmbClassStateData | undefined) => unknown} mappingFunction - Method to return the part for this Observable to return. + * @param {(previousResult: unknown, currentResult: unknown) => boolean} [memoizationFunction] - Method to Compare if the data has changed. Should return true when data is different. + * @returns {Observable} - an observable. * @description - Creates an Observable from this State. */ asObservablePart( @@ -33,7 +30,7 @@ export class UmbClassState extends UmbB /** * @function setValue - * @param {T} data - The next data for this state to hold. + * @param {UmbClassStateData | undefined} data - The next data for this state to hold. * @description - Set the data of this state, if data is different than current this will trigger observations to update. */ override setValue(data: T): void { diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/class-equal-memoization.function.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/class-equal-memoization.function.ts new file mode 100644 index 0000000000..ab4cdada11 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/class-equal-memoization.function.ts @@ -0,0 +1,20 @@ +/** + * @function classEqualMemoization + * @param {UmbClassStateData | undefined} previousValue - The previous value to compare. + * @param {UmbClassStateData | undefined} currentValue - The current value to compare. + * @returns {boolean} - Returns true if the values are identical. + * @description - Compares the two values using strict equality. + */ +export function classEqualMemoization( + previousValue: UmbClassStateData | undefined, + currentValue: UmbClassStateData | undefined, +): boolean { + return ( + (previousValue && currentValue && previousValue.equal(currentValue)) || + (previousValue === undefined && currentValue === undefined) + ); +} + +export interface UmbClassStateData { + equal(otherClass: this | undefined): boolean; +} diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/create-observable-part.function.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/create-observable-part.function.ts index d8762bf36d..f878d7cced 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/create-observable-part.function.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/create-observable-part.function.ts @@ -9,6 +9,7 @@ import { distinctUntilChanged, map, shareReplay } from '@umbraco-cms/backoffice/ * @param {Observable} source - RxJS Subject to use for this Observable. * @param {(mappable: T) => R} mappingFunction - Method to return the part for this Observable to return. * @param {(previousResult: R, currentResult: R) => boolean} [memoizationFunction] - Method to Compare if the data has changed. Should return true when data is different. + * @returns {Observable} * @description - Creates a RxJS Observable from RxJS Subject. * @example Example create a Observable for part of the data Subject. * public readonly myPart = CreateObservablePart(this._data, (data) => data.myPart); diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts index ff6dfc7e0d..03ffd9d6e6 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/utils/index.ts @@ -1,5 +1,6 @@ export * from './append-to-frozen-array.function.js'; export * from './assign-to-frozen-object.function.js'; +export * from './class-equal-memoization.function.js'; export * from './create-observable-part.function.js'; export * from './deep-freeze.function.js'; export * from './default-memoization.function.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/block-grid-area-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/block-grid-area-type-workspace.context.ts index 0acaf4f569..906c503a57 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/block-grid-area-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/block-grid-area-type-workspace.context.ts @@ -1,5 +1,5 @@ import type { UmbBlockGridTypeAreaType } from '../../../types.js'; -import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property'; +import type { UmbPropertyDatasetContext, UmbPropertyValueData } from '@umbraco-cms/backoffice/property'; import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; import type { UmbInvariantDatasetWorkspaceContext, @@ -10,12 +10,14 @@ import type { import { UmbSubmittableWorkspaceContextBase, UmbInvariantWorkspacePropertyDatasetContext, + umbObjectToPropertyValueArray, } from '@umbraco-cms/backoffice/workspace'; import { UmbArrayState, UmbObjectState, appendToFrozenArray } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; import type { PropertyEditorSettingsProperty } from '@umbraco-cms/backoffice/property-editor'; import { UmbId } from '@umbraco-cms/backoffice/id'; +import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; export class UmbBlockGridAreaTypeWorkspaceContext extends UmbSubmittableWorkspaceContextBase @@ -28,6 +30,13 @@ export class UmbBlockGridAreaTypeWorkspaceContext #data = new UmbObjectState(undefined); readonly data = this.#data.asObservable(); + readonly values = this.#data.asObservablePart((data) => { + return umbObjectToPropertyValueArray(data); + }); + async getValues(): Promise | undefined> { + return umbObjectToPropertyValueArray(await firstValueFrom(this.data)); + } + // TODO: Get the name of the contentElementType.. readonly name = this.#data.asObservablePart((data) => data?.alias); readonly unique = this.#data.asObservablePart((data) => data?.key); diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts index e8d2b915e8..10eb00ca0a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/manifests.ts @@ -1,6 +1,7 @@ import { manifests as workspaceViewManifests } from './views/manifests.js'; import { UMB_BLOCK_GRID_AREA_TYPE_WORKSPACE_ALIAS } from './index.js'; import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ ...workspaceViewManifests, @@ -27,7 +28,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_BLOCK_GRID_AREA_TYPE_WORKSPACE_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts index e30f10ceda..ba7238c442 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-area-config-entry/workspace/views/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_BLOCK_GRID_AREA_TYPE_WORKSPACE_ALIAS } from '../index.js'; import type { ManifestWorkspaceView } from '@umbraco-cms/backoffice/workspace'; @@ -15,7 +16,7 @@ export const workspaceViews: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_BLOCK_GRID_AREA_TYPE_WORKSPACE_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts index 36be37cd9b..82e3f4333f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts @@ -1,12 +1,27 @@ import { UMB_BLOCK_GRID_ENTRY_CONTEXT } from '../../context/block-grid-entry.context-token.js'; -import { UmbBlockGridInlinePropertyDatasetContext } from './block-grid-inline-property-dataset.context.js'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type'; import '../block-grid-areas-container/index.js'; import '../ref-grid-block/index.js'; import type { UmbBlockEditorCustomViewConfiguration } from '@umbraco-cms/backoffice/block-custom-view'; -import type { UmbBlockDataType } from '@umbraco-cms/backoffice/block'; +import { + type UMB_BLOCK_WORKSPACE_CONTEXT, + UMB_BLOCK_WORKSPACE_ALIAS, + type UmbBlockDataType, +} from '@umbraco-cms/backoffice/block'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { + UmbExtensionApiInitializer, + UmbExtensionsApiInitializer, + type UmbApiConstructorArgumentsMethodType, +} from '@umbraco-cms/backoffice/extension-api'; +import { UmbLanguageItemRepository } from '@umbraco-cms/backoffice/language'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; + +const apiArgsCreator: UmbApiConstructorArgumentsMethodType = (manifest: unknown) => { + return [{ manifest }]; +}; /** * @element umb-block-grid-block-inline @@ -14,15 +29,19 @@ import type { UmbBlockDataType } from '@umbraco-cms/backoffice/block'; @customElement('umb-block-grid-block-inline') export class UmbBlockGridBlockInlineElement extends UmbLitElement { // + #blockContext?: typeof UMB_BLOCK_GRID_ENTRY_CONTEXT.TYPE; + #workspaceContext?: typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE; + #contentKey?: string; + @property({ attribute: false }) + config?: UmbBlockEditorCustomViewConfiguration; + + @property({ type: String, reflect: false }) label?: string; @property({ type: String, reflect: false }) icon?: string; - @property({ attribute: false }) - config?: UmbBlockEditorCustomViewConfiguration; - @property({ type: Boolean, reflect: true }) unpublished?: boolean; @@ -30,38 +49,137 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement { content?: UmbBlockDataType; @state() - _inlineProperty: UmbPropertyTypeModel | undefined; + _inlineProperty?: UmbPropertyTypeModel; + + @state() + private _ownerContentTypeName?: string; + + @state() + private _variantName?: string; constructor() { super(); - this.consumeContext(UMB_BLOCK_GRID_ENTRY_CONTEXT, (context) => { - new UmbBlockGridInlinePropertyDatasetContext(this, context); - + this.consumeContext(UMB_BLOCK_GRID_ENTRY_CONTEXT, (blockContext) => { + this.#blockContext = blockContext; this.observe( - context.firstPropertyType, - (property) => { - this._inlineProperty = property; + this.#blockContext.unique, + (contentKey) => { + this.#contentKey = contentKey; + this.#load(); }, - 'inlineProperty', + 'observeContentKey', ); }); + new UmbExtensionApiInitializer( + this, + umbExtensionsRegistry, + UMB_BLOCK_WORKSPACE_ALIAS, + apiArgsCreator, + (permitted, ctrl) => { + const context = ctrl.api as typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE; + if (permitted && context) { + this.#workspaceContext = context; + this.#workspaceContext.establishLiveSync(); + + this.#load(); + + this.observe( + this.#workspaceContext.content.structure.contentTypeProperties, + (contentTypeProperties) => { + this._inlineProperty = contentTypeProperties[0]; + }, + 'observeProperties', + ); + + this.observe( + context.content.structure.ownerContentTypeName, + (name) => { + this._ownerContentTypeName = name; + }, + 'observeContentTypeName', + ); + + this.observe( + context.variantId, + async (variantId) => { + if (variantId) { + context.content.setup(this, variantId); + // TODO: Support segment name? + const culture = variantId.culture; + if (culture) { + const languageRepository = new UmbLanguageItemRepository(this); + const { data } = await languageRepository.requestItems([culture]); + const name = data?.[0].name; + this._variantName = name ? this.localize.string(name) : undefined; + } + } + }, + 'observeVariant', + ); + + new UmbExtensionsApiInitializer(this, umbExtensionsRegistry, 'workspaceContext', [ + this, + this.#workspaceContext, + ]); + } + }, + ); } + #load() { + if (!this.#workspaceContext || !this.#contentKey) return; + this.#workspaceContext.load(this.#contentKey); + } + + #expose = () => { + this.#workspaceContext?.expose(); + }; + override render() { - return html` - - - - - `; + return html` +
+ +
${this.#renderInside()}
+
+ `; + } + + #renderBlockInfo() { + return html` + + + + +
+ +
+
+ `; + } + + #renderInside() { + if (this.unpublished === true) { + return html` + `; + } else { + return html` + `; + } } static override styles = [ + UmbTextStyles, css` umb-block-grid-areas-container { margin-top: calc(var(--uui-size-2) + 1px); @@ -69,10 +187,114 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement { umb-block-grid-areas-container::part(area) { margin: var(--uui-size-2); } - :host([unpublished]) umb-icon, - :host([unpublished]) umb-ufm-render { + + #exposeButton { + width: 100%; + min-height: var(--uui-size-layout-3); + } + + #host { + position: relative; + display: block; + width: 100%; + + box-sizing: border-box; + border-radius: var(--uui-border-radius); + background-color: var(--uui-color-surface); + + border: 1px solid var(--uui-color-border); + transition: border-color 80ms; + + min-width: 250px; + } + #open-part + * { + border-top: 1px solid var(--uui-color-border); + } + :host([disabled]) #open-part { + cursor: default; + transition: border-color 80ms; + } + :host(:not([disabled])) #host:has(#open-part:hover) { + border-color: var(--uui-color-border-emphasis); + } + :host(:not([disabled])) #open-part:hover + * { + border-color: var(--uui-color-border-emphasis); + } + :host([disabled]) #host { + border-color: var(--uui-color-disabled-standalone); + } + + :host([unpublished]) #open-part { opacity: 0.6; } + + slot[name='tag'] { + flex-grow: 1; + + display: flex; + justify-content: flex-end; + align-items: center; + } + + button { + font-size: inherit; + font-family: inherit; + border: 0; + padding: 0; + background-color: transparent; + text-align: left; + color: var(--uui-color-text); + } + + #content { + align-self: stretch; + line-height: normal; + display: flex; + position: relative; + align-items: center; + } + + #open-part { + color: inherit; + text-decoration: none; + cursor: pointer; + + display: flex; + text-align: left; + align-items: center; + justify-content: flex-start; + width: 100%; + border: none; + background: none; + + min-height: var(--uui-size-16); + padding: calc(var(--uui-size-2) + 1px); + } + + #icon { + font-size: 1.2em; + margin-left: var(--uui-size-2); + margin-right: var(--uui-size-1); + } + + :host(:not([disabled])) #open-part:hover #icon { + color: var(--uui-color-interactive-emphasis); + } + :host(:not([disabled])) #open-part:hover #name { + color: var(--uui-color-interactive-emphasis); + } + + :host([disabled]) #icon { + color: var(--uui-color-disabled-contrast); + } + :host([disabled]) #name { + color: var(--uui-color-disabled-contrast); + } + + #inside { + position: relative; + display: block; + } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-inline-property-dataset.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-inline-property-dataset.context-token.ts deleted file mode 100644 index 56cb885b93..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-inline-property-dataset.context-token.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { UmbBlockGridInlinePropertyDatasetContext } from './block-grid-inline-property-dataset.context.js'; -import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; - -// TODO: Add discriminator: -export const UMB_BLOCK_GRID_INLINE_PROPERTY_DATASET_CONTEXT = - new UmbContextToken('UmbPropertyDatasetContext'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-inline-property-dataset.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-inline-property-dataset.context.ts deleted file mode 100644 index a320f9914a..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-inline-property-dataset.context.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { UmbBlockGridEntryContext } from '../../context/block-grid-entry.context.js'; -import { UMB_BLOCK_GRID_INLINE_PROPERTY_DATASET_CONTEXT } from './block-grid-inline-property-dataset.context-token.js'; -import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property'; -import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; -import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; -import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api'; - -export class UmbBlockGridInlinePropertyDatasetContext extends UmbControllerBase implements UmbPropertyDatasetContext { - #entryContext: UmbBlockGridEntryContext; - - #readOnly = new UmbBooleanState(false); - public readOnly = this.#readOnly.asObservable(); - - // default data: - - getVariantId() { - return UmbVariantId.CreateInvariant(); - } - getEntityType() { - return this.#entryContext.getEntityType(); - } - getUnique() { - return this.#entryContext.getUnique(); - } - - getName(): string | undefined { - return 'TODO: get label'; - } - readonly name: Observable = 'TODO: get label observable' as any; - - constructor(host: UmbControllerHost, entryContext: UmbBlockGridEntryContext) { - // The controller alias, is a very generic name cause we want only one of these for this controller host. - super(host, UMB_PROPERTY_DATASET_CONTEXT.toString()); - this.#entryContext = entryContext; - - this.provideContext(UMB_BLOCK_GRID_INLINE_PROPERTY_DATASET_CONTEXT, this); - } - - /** - * @function propertyValueByAlias - * @param {string} propertyAlias - * @returns {Promise | undefined>} - * @description Get an Observable for the value of this property. - */ - async propertyValueByAlias(propertyAlias: string) { - // TODO: Investigate how I do that with the workspaces.. - return await this.#entryContext.contentPropertyValueByAlias(propertyAlias); - } - - /** - * @function setPropertyValue - * @param {string} propertyAlias - * @param {unknown} value - value can be a promise resolving into the actual value or the raw value it self. - * @returns {Promise} - * @description Set the value of this property. - */ - async setPropertyValue(propertyAlias: string, value: unknown) { - // TODO: Investigate how I do that with the workspaces.. - return this.#entryContext.setContentPropertyValue(propertyAlias, value); - } - - /** - * Gets the read-only state of the current variant culture. - * @returns {*} {boolean} - * @memberof UmbBlockGridInlinePropertyDatasetContext - */ - getReadOnly(): boolean { - return this.#readOnly.getValue(); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts index 57047f4b4e..6c4a76df0e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entries/block-grid-entries.element.ts @@ -149,7 +149,7 @@ export class UmbBlockGridEntriesElement extends UmbFormControlMixin(UmbLitElemen // Currently there is no server validation for areas. So we can leave out the data path for it for now. [NL] this.#controlValidator = new UmbFormControlValidator(this, this); - //new UmbBindServerValidationToFormControl(this, this, "$.values.[?(@.alias = 'my-input-alias')].value"); + //new UmbBindServerValidationToFormControl(this, this, "$.values.[?(@.alias == 'my-input-alias')].value"); } } public get areaKey(): string | null | undefined { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts index 5bd07e2aba..4e39f774d5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts @@ -1,5 +1,5 @@ import { UmbBlockGridEntryContext } from '../../context/block-grid-entry.context.js'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbLitElement, umbDestroyOnDisconnect } from '@umbraco-cms/backoffice/lit-element'; import { html, css, customElement, property, state, nothing } from '@umbraco-cms/backoffice/external/lit'; import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor'; @@ -61,6 +61,9 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper @state() _contentTypeAlias?: string; + @state() + _contentTypeName?: string; + @state() _columnSpan?: number; @@ -325,6 +328,13 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper }, 'contentElementTypeAlias', ); + this.observe( + this.#context.contentElementTypeName, + (contentElementTypeName) => { + this._contentTypeName = contentElementTypeName; + }, + 'contentElementTypeName', + ); this.#callUpdateInlineCreateButtons(); } @@ -336,6 +346,10 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper } } + #expose = () => { + this.#context.expose(); + }; + #callUpdateInlineCreateButtons() { clearTimeout(this.#renderTimeout); this.#renderTimeout = setTimeout(this.#updateInlineCreateButtons, 100) as unknown as number; @@ -400,7 +414,8 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper .unpublished=${!this._exposed} .config=${this._blockViewProps.config} .content=${this._blockViewProps.content} - .settings=${this._blockViewProps.settings}>`; + .settings=${this._blockViewProps.settings} + ${umbDestroyOnDisconnect()}>`; } #renderRefBlock() { @@ -411,7 +426,8 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper .unpublished=${!this._exposed} .config=${this._blockViewProps.config} .content=${this._blockViewProps.content} - .settings=${this._blockViewProps.settings}>`; + .settings=${this._blockViewProps.settings} + ${umbDestroyOnDisconnect()}>`; } #renderBlock() { @@ -479,20 +495,25 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper } #renderEditAction() { - return html` - ${this._showContentEdit && this._workspaceEditContentPath + return this._showContentEdit && this._workspaceEditContentPath + ? html` + + ${this._contentInvalid + ? html`!` + : nothing} + ` + : this._showContentEdit === false && this._exposed === false ? html` - - ${this._contentInvalid - ? html`!` - : nothing} - ` - : nothing} - `; + >` + : nothing; } #renderEditSettingsAction() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/constants.ts new file mode 100644 index 0000000000..9f70d25c93 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/constants.ts @@ -0,0 +1,2 @@ +export const UMB_BLOCK_GRID_TYPE = 'block-grid-type'; +export const UMB_BLOCK_GRID = 'block-grid'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entry.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entry.context.ts index fdda04c514..a458b7f49c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entry.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-entry.context.ts @@ -9,13 +9,11 @@ import { UmbArrayState, UmbBooleanState, UmbNumberState, - UmbObjectState, appendToFrozenArray, mergeObservables, observeMultiple, } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import type { UmbContentTypeModel, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type'; import { UmbBlockEntryContext } from '@umbraco-cms/backoffice/block'; import type { UmbBlockGridTypeModel, UmbBlockGridLayoutModel } from '@umbraco-cms/backoffice/block-grid'; @@ -68,9 +66,6 @@ export class UmbBlockGridEntryContext ([a, b]) => a === true && b === false, ); - #firstPropertyType = new UmbObjectState(undefined); - readonly firstPropertyType = this.#firstPropertyType.asObservable(); - readonly scaleManager = new UmbBlockGridScaleManager(this); constructor(host: UmbControllerHost) { @@ -253,9 +248,7 @@ export class UmbBlockGridEntryContext ); } - _gotContentType(contentType: UmbContentTypeModel | undefined) { - this.#firstPropertyType.setValue(contentType?.properties[0]); - } + _gotContentType() {} #calcColumnSpan(columnSpan: number, relevantColumnSpanOptions: number[], layoutColumns: number) { if (relevantColumnSpanOptions.length > 0) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts index 7a34386848..2f411cfd2b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/context/block-grid-manager.context.ts @@ -186,7 +186,7 @@ export class UmbBlockGridManagerContext< originData: UmbBlockGridWorkspaceOriginData, ) { this.setOneLayout(layoutEntry, originData); - this.insertBlockData(layoutEntry, content, settings); + this.insertBlockData(layoutEntry, content, settings, originData); return true; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/index.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/index.ts index 577eea75f4..79d559f5c7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/index.ts @@ -1,3 +1,4 @@ +export * from './constants.js'; export * from './context/index.js'; -export * from './types.js'; export * from './workspace/index.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/types.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/types.ts index 5f192aedfe..b22f5a7b07 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/types.ts @@ -1,9 +1,6 @@ import type { UmbBlockLayoutBaseModel, UmbBlockValueType } from '@umbraco-cms/backoffice/block'; import type { UmbBlockTypeWithGroupKey } from '@umbraco-cms/backoffice/block-type'; -export const UMB_BLOCK_GRID_TYPE = 'block-grid-type'; -export const UMB_BLOCK_GRID = 'block-grid'; - // Configuration models: export interface UmbBlockGridTypeModel extends UmbBlockTypeWithGroupKey { columnSpanOptions: Array; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/block-grid-type-workspace.modal-token.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/block-grid-type-workspace.modal-token.ts index 61b344ca99..fdd64d49ad 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/block-grid-type-workspace.modal-token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/block-grid-type-workspace.modal-token.ts @@ -1,4 +1,5 @@ -import { UMB_BLOCK_GRID_TYPE, type UmbBlockGridTypeModel } from '../types.js'; +import type { UmbBlockGridTypeModel } from '../types.js'; +import { UMB_BLOCK_GRID_TYPE } from '../constants.js'; import type { UmbWorkspaceModalData, UmbWorkspaceModalValue } from '@umbraco-cms/backoffice/workspace'; import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/views/block-grid-type-workspace-view-advanced.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/views/block-grid-type-workspace-view-advanced.element.ts index 9879949f7c..c11ea6c9b8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/views/block-grid-type-workspace-view-advanced.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/views/block-grid-type-workspace-view-advanced.element.ts @@ -1,4 +1,4 @@ -import { UMB_BLOCK_GRID } from '../../types.js'; +import { UMB_BLOCK_GRID } from '../../constants.js'; import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/views/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/views/manifests.ts index a81da4e20d..8722b9f764 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/views/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-grid/workspace/views/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_BLOCK_GRID_TYPE_WORKSPACE_ALIAS } from '../index.js'; export const manifests: Array = [ @@ -14,7 +15,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_BLOCK_GRID_TYPE_WORKSPACE_ALIAS, }, ], @@ -32,7 +33,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_BLOCK_GRID_TYPE_WORKSPACE_ALIAS, }, ], @@ -50,7 +51,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_BLOCK_GRID_TYPE_WORKSPACE_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts index 19aa589759..f23b039e1b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/block-list-entry/block-list-entry.element.ts @@ -1,6 +1,7 @@ import { UmbBlockListEntryContext } from '../../context/block-list-entry.context.js'; -import { UMB_BLOCK_LIST, type UmbBlockListLayoutModel } from '../../types.js'; -import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import type { UmbBlockListLayoutModel } from '../../types.js'; +import { UMB_BLOCK_LIST } from '../../constants.js'; +import { UmbLitElement, umbDestroyOnDisconnect } from '@umbraco-cms/backoffice/lit-element'; import { html, css, customElement, property, state, nothing } from '@umbraco-cms/backoffice/external/lit'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor'; import '../ref-list-block/index.js'; @@ -53,6 +54,9 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper @state() _contentTypeAlias?: string; + @state() + _contentTypeName?: string; + @state() _showContentEdit = false; @state() @@ -247,8 +251,19 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper }, 'contentElementTypeAlias', ); + this.observe( + this.#context.contentElementTypeName, + (contentElementTypeName) => { + this._contentTypeName = contentElementTypeName; + }, + 'contentElementTypeName', + ); } + #expose = () => { + this.#context.expose(); + }; + #extensionSlotFilterMethod = (manifest: ManifestBlockEditorCustomView) => { if ( manifest.forContentTypeAlias && @@ -267,8 +282,10 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper .label=${this._label} .icon=${this._icon} .unpublished=${!this._exposed} + .config=${this._blockViewProps.config} .content=${this._blockViewProps.content} - .settings=${this._blockViewProps.settings}>`; + .settings=${this._blockViewProps.settings} + ${umbDestroyOnDisconnect()}>`; } #renderInlineBlock() { @@ -276,8 +293,10 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper .label=${this._label} .icon=${this._icon} .unpublished=${!this._exposed} + .config=${this._blockViewProps.config} .content=${this._blockViewProps.content} - .settings=${this._blockViewProps.settings}>`; + .settings=${this._blockViewProps.settings} + ${umbDestroyOnDisconnect()}>`; } #renderBlock() { @@ -300,7 +319,7 @@ export class UmbBlockListEntryElement extends UmbLitElement implements UmbProper } #renderEditContentAction() { - return html` ${this._showContentEdit && this._workspaceEditContentPath + return this._showContentEdit && this._workspaceEditContentPath ? html`!` : nothing} ` - : nothing}`; + : this._showContentEdit === false && this._exposed === false + ? html`` + : nothing; } #renderEditSettingsAction() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts index 9c7ae7378f..b0685a4828 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts @@ -4,13 +4,22 @@ import { type UmbBlockDataType, type UMB_BLOCK_WORKSPACE_CONTEXT, } from '@umbraco-cms/backoffice/block'; -import { UmbExtensionsApiInitializer, createExtensionApi } from '@umbraco-cms/backoffice/extension-api'; +import { + UmbExtensionApiInitializer, + UmbExtensionsApiInitializer, + type UmbApiConstructorArgumentsMethodType, +} from '@umbraco-cms/backoffice/extension-api'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import '../../../block/workspace/views/edit/block-workspace-view-edit-content-no-router.element.js'; +import { UmbLanguageItemRepository } from '@umbraco-cms/backoffice/language'; + +const apiArgsCreator: UmbApiConstructorArgumentsMethodType = (manifest: unknown) => { + return [{ manifest }]; +}; /** * @element umb-inline-list-block @@ -33,9 +42,18 @@ export class UmbInlineListBlockElement extends UmbLitElement { @property({ attribute: false }) content?: UmbBlockDataType; + @state() + private _exposed?: boolean; + @state() _isOpen = false; + @state() + private _ownerContentTypeName?: string; + + @state() + private _variantName?: string; + constructor() { super(); @@ -50,22 +68,60 @@ export class UmbInlineListBlockElement extends UmbLitElement { 'observeContentKey', ); }); - this.observe(umbExtensionsRegistry.byTypeAndAlias('workspace', UMB_BLOCK_WORKSPACE_ALIAS), (manifest) => { - if (manifest) { - createExtensionApi(this, manifest, [{ manifest: manifest }]).then((context) => { - if (context) { - this.#workspaceContext = context as typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE; - this.#workspaceContext.establishLiveSync(); - this.#load(); - new UmbExtensionsApiInitializer(this, umbExtensionsRegistry, 'workspaceContext', [ - this, - this.#workspaceContext, - ]); - } - }); - } - }); + new UmbExtensionApiInitializer( + this, + umbExtensionsRegistry, + UMB_BLOCK_WORKSPACE_ALIAS, + apiArgsCreator, + (permitted, ctrl) => { + const context = ctrl.api as typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE; + if (permitted && context) { + this.#workspaceContext = context; + this.#workspaceContext.establishLiveSync(); + this.#load(); + + this.observe( + this.#workspaceContext.exposed, + (exposed) => { + this._exposed = exposed; + }, + 'observeExposed', + ); + + this.observe( + context.content.structure.ownerContentTypeName, + (name) => { + this._ownerContentTypeName = name; + }, + 'observeContentTypeName', + ); + + this.observe( + context.variantId, + async (variantId) => { + if (variantId) { + context.content.setup(this, variantId); + // TODO: Support segment name? + const culture = variantId.culture; + if (culture) { + const languageRepository = new UmbLanguageItemRepository(this); + const { data } = await languageRepository.requestItems([culture]); + const name = data?.[0].name; + this._variantName = name ? this.localize.string(name) : undefined; + } + } + }, + 'observeVariant', + ); + + new UmbExtensionsApiInitializer(this, umbExtensionsRegistry, 'workspaceContext', [ + this, + this.#workspaceContext, + ]); + } + }, + ); } #load() { @@ -73,6 +129,10 @@ export class UmbInlineListBlockElement extends UmbLitElement { this.#workspaceContext.load(this.#contentKey); } + #expose = () => { + this.#workspaceContext?.expose(); + }; + override render() { return html`
@@ -89,18 +149,16 @@ export class UmbInlineListBlockElement extends UmbLitElement { this._isOpen = !this._isOpen; }}> - ${this.#renderContent()} + ${this.#renderBlockInfo()} - ${this._isOpen === true - ? html`` - : ''} + ${this._isOpen === true ? this.#renderInside() : ''}
`; } - #renderContent() { + #renderBlockInfo() { return html` @@ -113,6 +171,19 @@ export class UmbInlineListBlockElement extends UmbLitElement { `; } + #renderInside() { + if (this._exposed === false) { + return html` + `; + } else { + return html``; + } + } + static override styles = [ UmbTextStyles, css` diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/ref-list-block/ref-list-block.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/ref-list-block/ref-list-block.element.ts index 556d67460f..2e9695f545 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/ref-list-block/ref-list-block.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/components/ref-list-block/ref-list-block.element.ts @@ -1,13 +1,10 @@ import { css, customElement, html, property } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UMB_BLOCK_ENTRY_CONTEXT } from '@umbraco-cms/backoffice/block'; import type { UmbBlockDataType } from '@umbraco-cms/backoffice/block'; import '@umbraco-cms/backoffice/ufm'; +import type { UmbBlockEditorCustomViewConfiguration } from '@umbraco-cms/backoffice/block-custom-view'; -/** - * @element umb-ref-list-block - */ @customElement('umb-ref-list-block') export class UmbRefListBlockElement extends UmbLitElement { // @@ -23,27 +20,12 @@ export class UmbRefListBlockElement extends UmbLitElement { @property({ attribute: false }) content?: UmbBlockDataType; - @property() - _workspaceEditPath?: string; - - constructor() { - super(); - - // UMB_BLOCK_LIST_ENTRY_CONTEXT - this.consumeContext(UMB_BLOCK_ENTRY_CONTEXT, async (context) => { - this.observe( - context.workspaceEditContentPath, - (workspaceEditPath) => { - this._workspaceEditPath = workspaceEditPath; - }, - 'observeWorkspaceEditPath', - ); - }); - } + @property({ attribute: false }) + config?: UmbBlockEditorCustomViewConfiguration; override render() { return html` - + diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/constants.ts new file mode 100644 index 0000000000..cae284bdee --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/constants.ts @@ -0,0 +1,2 @@ +export const UMB_BLOCK_LIST_TYPE = 'block-list-type'; +export const UMB_BLOCK_LIST = 'block-list'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-entries.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-entries.context.ts index 2e20c5836e..0ddd1eb326 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-entries.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-entries.context.ts @@ -48,7 +48,11 @@ export class UmbBlockListEntriesContext extends UmbBlockEntriesContext< }) .onSubmit(async (value, data) => { if (value?.create && data) { - const created = await this.create(value.create.contentElementTypeKey, {}); + const created = await this.create( + value.create.contentElementTypeKey, + {}, + data.originData as UmbBlockListWorkspaceOriginData, + ); if (created) { this.insert( created.layout, @@ -127,9 +131,13 @@ export class UmbBlockListEntriesContext extends UmbBlockEntriesContext< this._manager?.setLayouts(layouts); } - async create(contentElementTypeKey: string, partialLayoutEntry?: Omit) { + async create( + contentElementTypeKey: string, + partialLayoutEntry?: Omit, + originData?: UmbBlockListWorkspaceOriginData, + ) { await this._retrieveManager; - return this._manager?.create(contentElementTypeKey, partialLayoutEntry); + return this._manager?.create(contentElementTypeKey, partialLayoutEntry, originData); } // insert Block? diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts index b2b8d3d065..ccc98f903a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/context/block-list-manager.context.ts @@ -21,7 +21,13 @@ export class UmbBlockListManagerContext< return this.#inlineEditingMode.getValue(); } - create(contentElementTypeKey: string, partialLayoutEntry?: Omit) { + create( + contentElementTypeKey: string, + partialLayoutEntry?: Omit, + // This property is used by some implementations, but not used in this. Do not remove. [NL] + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _originData?: UmbBlockListWorkspaceOriginData, + ) { return super._createBlockData(contentElementTypeKey, partialLayoutEntry); } @@ -33,7 +39,7 @@ export class UmbBlockListManagerContext< ) { this._layouts.appendOneAt(layoutEntry, originData.index ?? -1); - this.insertBlockData(layoutEntry, content, settings); + this.insertBlockData(layoutEntry, content, settings, originData); return true; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/index.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/index.ts index 577eea75f4..79d559f5c7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/index.ts @@ -1,3 +1,4 @@ +export * from './constants.js'; export * from './context/index.js'; -export * from './types.js'; export * from './workspace/index.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts index f86b7c72c3..67e1291272 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-editor/property-editor-ui-block-list.element.ts @@ -117,7 +117,6 @@ export class UmbPropertyEditorUIBlockListElement /** * Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content. * @type {boolean} - * @attr * @default */ @property({ type: Boolean, reflect: true }) @@ -155,8 +154,6 @@ export class UmbPropertyEditorUIBlockListElement constructor() { super(); - //this.#validationContext.messages.debug('block list'); - this.consumeContext(UMB_PROPERTY_CONTEXT, (context) => { this.observe( context.dataPath, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.element.ts index 772b8068c2..054b47d124 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/property-editors/block-list-type-configuration/property-editor-ui-block-list-type-configuration.element.ts @@ -1,5 +1,5 @@ import '../../../block-type/components/input-block-type/index.js'; -import { UMB_BLOCK_LIST_TYPE } from '../../types.js'; +import { UMB_BLOCK_LIST_TYPE } from '../../constants.js'; import type { UmbBlockTypeBaseModel, UmbInputBlockTypeElement } from '@umbraco-cms/backoffice/block-type'; import { type UmbPropertyEditorUiElement, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/types.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/types.ts index 94ff232c27..2903ee03b3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/types.ts @@ -1,9 +1,6 @@ import type { UmbBlockLayoutBaseModel, UmbBlockValueType } from '@umbraco-cms/backoffice/block'; import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type'; -export const UMB_BLOCK_LIST_TYPE = 'block-list-type'; -export const UMB_BLOCK_LIST = 'block-list'; - // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface UmbBlockListTypeModel extends UmbBlockTypeBaseModel {} // eslint-disable-next-line @typescript-eslint/no-empty-object-type diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/workspace/views/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/workspace/views/manifests.ts index 2668e8db6a..04d75d39ab 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-list/workspace/views/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-list/workspace/views/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_BLOCK_LIST_TYPE_WORKSPACE_ALIAS } from '../index.js'; export const manifests: Array = [ @@ -14,7 +15,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_BLOCK_LIST_TYPE_WORKSPACE_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts index f835c064c1..e5f8fd3486 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.ts @@ -1,7 +1,8 @@ -import { UMB_BLOCK_RTE, type UmbBlockRteLayoutModel } from '../../types.js'; +import type { UmbBlockRteLayoutModel } from '../../types.js'; +import { UMB_BLOCK_RTE } from '../../constants.js'; import { UmbBlockRteEntryContext } from '../../context/block-rte-entry.context.js'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { html, css, property, state, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { html, css, property, state, customElement, nothing } from '@umbraco-cms/backoffice/external/lit'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { @@ -11,6 +12,8 @@ import type { import { stringOrStringArrayContains } from '@umbraco-cms/backoffice/utils'; import '../ref-rte-block/index.js'; +import { UmbObserveValidationStateController } from '@umbraco-cms/backoffice/validation'; +import { UmbDataPathBlockElementDataQuery } from '@umbraco-cms/backoffice/block'; /** * @class UmbBlockRteEntryElement @@ -26,6 +29,16 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert if (!value) return; this._contentKey = value; this.#context.setContentKey(value); + + new UmbObserveValidationStateController( + this, + `$.contentData[${UmbDataPathBlockElementDataQuery({ key: value })}]`, + (hasMessages) => { + this._contentInvalid = hasMessages; + this._blockViewProps.contentInvalid = hasMessages; + }, + 'observeMessagesForContent', + ); } private _contentKey?: string | undefined; @@ -55,12 +68,23 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert @state() _contentElementTypeAlias?: string; + @state() + _contentTypeName?: string; + @state() _blockViewProps: UmbBlockEditorCustomViewProperties = { contentKey: undefined!, config: { showContentEdit: false, showSettingsEdit: false }, }; // Set to undefined cause it will be set before we render. + // 'content-invalid' attribute is used for styling purpose. + @property({ type: Boolean, attribute: 'content-invalid', reflect: true }) + _contentInvalid?: boolean; + + // 'settings-invalid' attribute is used for styling purpose. + @property({ type: Boolean, attribute: 'settings-invalid', reflect: true }) + _settingsInvalid?: boolean; + #updateBlockViewProps(incoming: Partial>) { this._blockViewProps = { ...this._blockViewProps, ...incoming }; this.requestUpdate('_blockViewProps'); @@ -72,17 +96,36 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert // We do not have index for RTE Blocks at the moment. this.#context.setIndex(0); - this.observe(this.#context.showContentEdit, (showContentEdit) => { - this._showContentEdit = showContentEdit; - this.#updateBlockViewProps({ config: { ...this._blockViewProps.config!, showContentEdit } }); - }); - this.observe(this.#context.settingsElementTypeKey, (key) => { - this._hasSettings = !!key; - this.#updateBlockViewProps({ config: { ...this._blockViewProps.config!, showSettingsEdit: !!key } }); - }); - this.observe(this.#context.contentElementTypeAlias, (alias) => { - this._contentElementTypeAlias = alias; - }); + this.observe( + this.#context.showContentEdit, + (showContentEdit) => { + this._showContentEdit = showContentEdit; + this.#updateBlockViewProps({ config: { ...this._blockViewProps.config!, showContentEdit } }); + }, + null, + ); + this.observe( + this.#context.settingsElementTypeKey, + (key) => { + this._hasSettings = !!key; + this.#updateBlockViewProps({ config: { ...this._blockViewProps.config!, showSettingsEdit: !!key } }); + }, + null, + ); + this.observe( + this.#context.contentElementTypeAlias, + (alias) => { + this._contentElementTypeAlias = alias; + }, + null, + ); + this.observe( + this.#context.contentElementTypeName, + (contentElementTypeName) => { + this._contentTypeName = contentElementTypeName; + }, + null, + ); this.observe( this.#context.blockType, (blockType) => { @@ -126,8 +169,6 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert this.#observeData(); - // TODO: Implement validation for RTE Blocks: [NL] - /* this.observe( this.#context.settingsKey, (settingsKey) => { @@ -147,7 +188,7 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert }, null, ); - */ + this.observe( this.#context.workspaceEditContentPath, (path) => { @@ -188,15 +229,6 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert this.setAttribute('contenteditable', 'false'); } - #renderRefBlock() { - return html``; - } - readonly #filterBlockCustomViews = (manifest: ManifestBlockEditorCustomView) => { const elementTypeAlias = this._contentElementTypeAlias ?? ''; const isForBlockEditor = @@ -206,6 +238,10 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert return isForBlockEditor && isForContentTypeAlias; }; + #expose = () => { + this.#context.expose(); + }; + #renderBlock() { return html`
@@ -217,22 +253,62 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert single> ${this.#renderRefBlock()} - - ${this._showContentEdit && this._workspaceEditContentPath - ? html` - - ` - : ''} - ${this._hasSettings && this._workspaceEditSettingsPath - ? html` - - ` - : ''} - + ${this.#renderEditAction()} ${this.#renderEditSettingsAction()} + ${!this._showContentEdit && this._contentInvalid + ? html`!` + : nothing}
`; } + #renderRefBlock() { + return html``; + } + + #renderEditAction() { + return this._showContentEdit && this._workspaceEditContentPath + ? html` + + ${this._contentInvalid + ? html`!` + : nothing} + ` + : this._showContentEdit === false && this._exposed === false + ? html`` + : nothing; + } + + #renderEditSettingsAction() { + return html` + ${this._hasSettings && this._workspaceEditSettingsPath + ? html` + + ${this._settingsInvalid + ? html`!` + : nothing} + ` + : nothing} + `; + } + override render() { return this.#renderBlock(); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/constants.ts new file mode 100644 index 0000000000..4aaa6a7406 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/constants.ts @@ -0,0 +1,2 @@ +export const UMB_BLOCK_RTE_TYPE = 'block-rte-type'; +export const UMB_BLOCK_RTE = 'block-rte'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-entries.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-entries.context.ts index 441f6c17fb..70846d23fd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-entries.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-entries.context.ts @@ -53,7 +53,12 @@ export class UmbBlockRteEntriesContext extends UmbBlockEntriesContext< {} as any, ); if (created) { - this.insert(created.layout, created.content, created.settings); + this.insert( + created.layout, + created.content, + created.settings, + data.originData as UmbBlockRteWorkspaceOriginData, + ); } else { throw new Error('Failed to create block'); } @@ -122,9 +127,13 @@ export class UmbBlockRteEntriesContext extends UmbBlockEntriesContext< this._manager?.setLayouts(layouts); } - async create(contentElementTypeKey: string, partialLayoutEntry?: Omit) { + async create( + contentElementTypeKey: string, + partialLayoutEntry?: Omit, + originData?: UmbBlockRteWorkspaceOriginData, + ) { await this._retrieveManager; - return this._manager?.create(contentElementTypeKey, partialLayoutEntry); + return this._manager?.create(contentElementTypeKey, partialLayoutEntry, originData); } // insert Block? @@ -133,9 +142,10 @@ export class UmbBlockRteEntriesContext extends UmbBlockEntriesContext< layoutEntry: UmbBlockRteLayoutModel, content: UmbBlockDataModel, settings: UmbBlockDataModel | undefined, + originData: UmbBlockRteWorkspaceOriginData, ) { await this._retrieveManager; - return this._manager?.insert(layoutEntry, content, settings) ?? false; + return this._manager?.insert(layoutEntry, content, settings, originData) ?? false; } // create Block? diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts index 876dfb06d8..4fefc64e27 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/context/block-rte-manager.context.ts @@ -1,3 +1,4 @@ +import type { UmbBlockRteWorkspaceOriginData } from '../workspace/block-rte-workspace.modal-token.js'; import type { UmbBlockRteLayoutModel, UmbBlockRteTypeModel } from '../types.js'; import type { UmbBlockDataModel } from '../../block/types.js'; import { UmbBlockManagerContext } from '@umbraco-cms/backoffice/block'; @@ -14,7 +15,13 @@ export class UmbBlockRteManagerContext< this._layouts.removeOne(contentKey); } - create(contentElementTypeKey: string, partialLayoutEntry?: Omit) { + create( + contentElementTypeKey: string, + partialLayoutEntry?: Omit, + // This property is used by some implementations, but not used in this, do not remove. [NL] + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _originData?: UmbBlockRteWorkspaceOriginData, + ) { const data = super._createBlockData(contentElementTypeKey, partialLayoutEntry); // Find block type. @@ -30,10 +37,15 @@ export class UmbBlockRteManagerContext< return data; } - insert(layoutEntry: BlockLayoutType, content: UmbBlockDataModel, settings: UmbBlockDataModel | undefined) { + insert( + layoutEntry: BlockLayoutType, + content: UmbBlockDataModel, + settings: UmbBlockDataModel | undefined, + originData: UmbBlockRteWorkspaceOriginData, + ) { this._layouts.appendOne(layoutEntry); - this.insertBlockData(layoutEntry, content, settings); + this.insertBlockData(layoutEntry, content, settings, originData); return true; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/index.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/index.ts index 5ce24a84b0..40cb3510e5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/index.ts @@ -1,4 +1,5 @@ +export * from './constants.js'; export * from './components/index.js'; export * from './context/index.js'; export * from './workspace/index.js'; -export * from './types.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/types.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/types.ts index f6e44b3beb..c7e8bff28b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/types.ts @@ -1,9 +1,6 @@ import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type'; import type { UmbBlockLayoutBaseModel, UmbBlockValueType } from '@umbraco-cms/backoffice/block'; -export const UMB_BLOCK_RTE_TYPE = 'block-rte-type'; -export const UMB_BLOCK_RTE = 'block-rte'; - export interface UmbBlockRteTypeModel extends UmbBlockTypeBaseModel { displayInline: boolean; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/workspace/views/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/workspace/views/manifests.ts index a6f11440a5..5b8abf2041 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/workspace/views/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-rte/workspace/views/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_BLOCK_RTE_TYPE_WORKSPACE_ALIAS } from '../index.js'; export const manifests: Array = [ @@ -14,7 +15,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_BLOCK_RTE_TYPE_WORKSPACE_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/block-type-card/block-type-card.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/block-type-card/block-type-card.element.ts index 5ec53ac6a3..376e077785 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/block-type-card/block-type-card.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/components/block-type-card/block-type-card.element.ts @@ -6,13 +6,14 @@ import { html, customElement, property, state, ifDefined } from '@umbraco-cms/ba import { UmbRepositoryItemsManager } from '@umbraco-cms/backoffice/repository'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; -import { removeLastSlashFromPath, transformServerPathToClientPath } from '@umbraco-cms/backoffice/utils'; +import { transformServerPathToClientPath } from '@umbraco-cms/backoffice/utils'; import { UUICardEvent } from '@umbraco-cms/backoffice/external/uui'; @customElement('umb-block-type-card') export class UmbBlockTypeCardElement extends UmbLitElement { - readonly #init: Promise; - #appUrl: string = ''; + // + #init: Promise; + #serverUrl: string = ''; readonly #itemManager = new UmbRepositoryItemsManager( this, @@ -28,7 +29,8 @@ export class UmbBlockTypeCardElement extends UmbLitElement { value = transformServerPathToClientPath(value); if (value) { this.#init.then(() => { - this._iconFile = removeLastSlashFromPath(this.#appUrl) + value; + const url = new URL(value, this.#serverUrl); + this._iconFile = url.href; }); } else { this._iconFile = undefined; @@ -76,7 +78,7 @@ export class UmbBlockTypeCardElement extends UmbLitElement { super(); this.#init = this.getContext(UMB_APP_CONTEXT).then((appContext) => { - this.#appUrl = appContext.getServerUrl() + appContext.getBackofficePath(); + this.#serverUrl = appContext.getServerUrl(); }); this.observe(this.#itemManager.items, (items) => { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/index.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/index.ts index 814adba5f3..76a7e33393 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/index.ts @@ -1,3 +1,3 @@ export * from './components/index.js'; -export * from './types.js'; export * from './workspace/index.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/workspace/block-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/workspace/block-type-workspace.context.ts index 8b9d425241..1d17eb81ec 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/workspace/block-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/workspace/block-type-workspace.context.ts @@ -1,6 +1,6 @@ import type { UmbBlockTypeBaseModel, UmbBlockTypeWithGroupKey } from '../types.js'; import { UmbBlockTypeWorkspaceEditorElement } from './block-type-workspace-editor.element.js'; -import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property'; +import type { UmbPropertyDatasetContext, UmbPropertyValueData } from '@umbraco-cms/backoffice/property'; import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property'; import type { UmbInvariantDatasetWorkspaceContext, @@ -12,9 +12,11 @@ import { UmbInvariantWorkspacePropertyDatasetContext, UmbWorkspaceIsNewRedirectController, UmbWorkspaceIsNewRedirectControllerAlias, + umbObjectToPropertyValueArray, } from '@umbraco-cms/backoffice/workspace'; import { UmbObjectState, appendToFrozenArray } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; export class UmbBlockTypeWorkspaceContext extends UmbSubmittableWorkspaceContextBase @@ -25,12 +27,18 @@ export class UmbBlockTypeWorkspaceContext(undefined); - //readonly data = this.#data.asObservable(); + readonly data = this.#data.asObservable(); - // TODO: Get the name of the contentElementType.. - readonly name = this.#data.asObservablePart(() => 'block'); + readonly name = this.#data.asObservablePart(() => 'block type'); readonly unique = this.#data.asObservablePart((data) => data?.contentElementTypeKey); + readonly values = this.#data.asObservablePart((data) => { + return umbObjectToPropertyValueArray(data); + }); + async getValues(): Promise | undefined> { + return umbObjectToPropertyValueArray(await firstValueFrom(this.data)); + } + constructor(host: UmbControllerHost, args: { manifest: ManifestWorkspace }) { super(host, args.manifest.alias); const manifest = args.manifest; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/workspace/manifests.ts index eadd29a17c..3fcdb6fb10 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block-type/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block-type/workspace/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_BLOCK_GRID_TYPE_WORKSPACE_ALIAS } from '../../block-grid/workspace/index.js'; import { UMB_BLOCK_LIST_TYPE_WORKSPACE_ALIAS } from '../../block-list/workspace/index.js'; import { UMB_BLOCK_RTE_TYPE_WORKSPACE_ALIAS } from '../../block-rte/workspace/index.js'; @@ -17,7 +18,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, oneOf: [ UMB_BLOCK_GRID_TYPE_WORKSPACE_ALIAS, UMB_BLOCK_LIST_TYPE_WORKSPACE_ALIAS, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts index 0d2189840a..1e5e0cd222 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-entry.context.ts @@ -46,10 +46,10 @@ export abstract class UmbBlockEntryContext< #contentKey?: string; #variantId = new UmbClassState(undefined); - protected _variantId = this.#variantId.asObservable(); + protected readonly _variantId = this.#variantId.asObservable(); #hasExpose = new UmbBooleanState(undefined); - hasExpose = this.#hasExpose.asObservable(); + readonly hasExpose = this.#hasExpose.asObservable(); public readonly readOnlyState = new UmbReadOnlyVariantStateManager(this); @@ -376,10 +376,10 @@ export abstract class UmbBlockEntryContext< /** * Get the current value of this Blocks label. - * @function getLabel + * @function getName * @returns {string} - the value of the label. */ - getLabel() { + getName() { return this.#labelRender.toString(); } @@ -638,7 +638,7 @@ export abstract class UmbBlockEntryContext< } async requestDelete() { - const blockName = this.getLabel(); + const blockName = this.getName(); // TODO: Localizations missing [NL] await umbConfirmModal(this, { headline: `Delete ${blockName}`, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts index c650cee4c0..4fb02dc221 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/context/block-manager.context.ts @@ -78,7 +78,7 @@ export abstract class UmbBlockManagerContext< setEditorConfiguration(configs: UmbPropertyEditorConfigCollection) { this._editorConfiguration.setValue(configs); if (this._liveEditingMode.getValue() === undefined) { - this._liveEditingMode.setValue(configs.getValueByAlias('liveEditingMode')); + this._liveEditingMode.setValue(configs.getValueByAlias('useLiveEditing')); } } getEditorConfiguration(): UmbPropertyEditorConfigCollection | undefined { @@ -206,7 +206,9 @@ export abstract class UmbBlockManagerContext< getContentOf(contentKey: string) { return this.#contents.value.find((x) => x.key === contentKey); } - setOneLayout(layoutData: BlockLayoutType) { + // originData param is used by some implementations. [NL] should be here, do not remove it. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + setOneLayout(layoutData: BlockLayoutType, _originData?: BlockOriginDataType) { this._layouts.appendOne(layoutData); } setOneContent(contentData: UmbBlockDataModel) { @@ -319,7 +321,13 @@ export abstract class UmbBlockManagerContext< originData: BlockOriginDataType, ): boolean; - protected insertBlockData(layoutEntry: BlockLayoutType, content: UmbBlockDataModel, settings?: UmbBlockDataModel) { + protected insertBlockData( + layoutEntry: BlockLayoutType, + content: UmbBlockDataModel, + settings: UmbBlockDataModel | undefined, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _originData: BlockOriginDataType, + ) { // Create content entry: if (layoutEntry.contentKey) { this.#contents.appendOne(content); diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/index.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/index.ts index 866baaf319..216c98bbe6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/index.ts @@ -1,6 +1,6 @@ export * from './context/index.js'; export * from './modals/index.js'; -export type * from './types.js'; +export * from './property-value-resolver/index.js'; export * from './validation/index.js'; export * from './workspace/index.js'; -export * from './property-value-resolver/index.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/property-value-resolver/block-value-resolver.api.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/property-value-resolver/block-value-resolver.api.ts index 08ffcdc3f6..7abc94574f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/property-value-resolver/block-value-resolver.api.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/property-value-resolver/block-value-resolver.api.ts @@ -1,14 +1,14 @@ import type { UmbBlockDataValueModel, UmbBlockExposeModel, UmbBlockValueDataPropertiesBaseType } from '../types.js'; -import type { UmbContentValueModel } from '@umbraco-cms/backoffice/content'; +import type { UmbElementValueModel } from '@umbraco-cms/backoffice/content'; import type { UmbPropertyValueResolver } from '@umbraco-cms/backoffice/property'; export abstract class UmbBlockValueResolver - implements UmbPropertyValueResolver, UmbBlockDataValueModel, UmbBlockExposeModel> + implements UmbPropertyValueResolver, UmbBlockDataValueModel, UmbBlockExposeModel> { abstract processValues( - property: UmbContentValueModel, + property: UmbElementValueModel, valuesCallback: (values: Array) => Promise | undefined>, - ): Promise>; + ): Promise>; protected async _processValueBlockData( value: ValueType, @@ -30,9 +30,9 @@ export abstract class UmbBlockValueResolver } abstract processVariants( - property: UmbContentValueModel, + property: UmbElementValueModel, variantsCallback: (values: Array) => Promise | undefined>, - ): Promise>; + ): Promise>; protected async _processVariantBlockData( value: ValueType, diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/property-value-resolver/standard-block-value-resolver.api.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/property-value-resolver/standard-block-value-resolver.api.ts index 7837845e70..2f3c42d1f8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/property-value-resolver/standard-block-value-resolver.api.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/property-value-resolver/standard-block-value-resolver.api.ts @@ -1,10 +1,10 @@ import type { UmbBlockDataValueModel, UmbBlockExposeModel, UmbBlockValueType } from '../types.js'; import { UmbBlockValueResolver } from './block-value-resolver.api.js'; -import type { UmbContentValueModel } from '@umbraco-cms/backoffice/content'; +import type { UmbElementValueModel } from '@umbraco-cms/backoffice/content'; export class UmbStandardBlockValueResolver extends UmbBlockValueResolver { async processValues( - property: UmbContentValueModel, + property: UmbElementValueModel, valuesCallback: (values: Array) => Promise | undefined>, ) { if (property.value) { @@ -17,7 +17,7 @@ export class UmbStandardBlockValueResolver extends UmbBlockValueResolver, + property: UmbElementValueModel, variantsCallback: (values: Array) => Promise | undefined>, ) { if (property.value) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/types.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/types.ts index a95b0dab57..011f9763f1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/types.ts @@ -1,4 +1,7 @@ -import type { UmbContentValueModel } from '@umbraco-cms/backoffice/content'; +import type { UmbElementValueModel } from '@umbraco-cms/backoffice/content'; + +export type * from './conditions/types.js'; +export type * from './clipboard/types.js'; export interface UmbBlockLayoutBaseModel { contentKey: string; @@ -6,7 +9,7 @@ export interface UmbBlockLayoutBaseModel { } // eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface UmbBlockDataValueModel extends UmbContentValueModel {} +export interface UmbBlockDataValueModel extends UmbElementValueModel {} export interface UmbBlockDataModel { key: string; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-data-property-validation-path-translator.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-data-property-validation-path-translator.controller.ts deleted file mode 100644 index 096eec3967..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-data-property-validation-path-translator.controller.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { - GetPropertyNameFromPath, - UmbDataPathPropertyValueQuery, - UmbValidationPathTranslatorBase, -} from '@umbraco-cms/backoffice/validation'; - -export class UmbBlockElementDataValidationPathTranslator extends UmbValidationPathTranslatorBase { - constructor(host: UmbControllerHost) { - super(host); - } - - translate(path: string) { - if (!this._context) return; - if (path.indexOf('$.') !== 0) { - // We do not handle this path. - return false; - } - - const rest = path.substring(2); - const key = GetPropertyNameFromPath(rest); - - const specificValue = { alias: key }; - // replace the values[ number ] with JSON-Path filter values[@.(...)], continues by the rest of the path: - //return '$.values' + UmbVariantValuesValidationPathTranslator(specificValue) + path.substring(path.indexOf(']')); - return '$.values[' + UmbDataPathPropertyValueQuery(specificValue) + '.value'; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-data-validation-path-translator.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-element-data-validation-path-translator.controller.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-data-validation-path-translator.controller.ts rename to src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-element-data-validation-path-translator.controller.ts diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-element-values-validation-path-translator.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-element-values-validation-path-translator.controller.ts new file mode 100644 index 0000000000..2c2b9da88c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/block-element-values-validation-path-translator.controller.ts @@ -0,0 +1,17 @@ +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { + UmbAbstractArrayValidationPathTranslator, + UmbDataPathPropertyValueQuery, +} from '@umbraco-cms/backoffice/validation'; + +export class UmbBlockElementValuesDataValidationPathTranslator extends UmbAbstractArrayValidationPathTranslator { + constructor(host: UmbControllerHost) { + super(host, '$.values[', UmbDataPathPropertyValueQuery); + } + + getDataFromIndex(index: number) { + if (!this._context) return; + const data = this._context.getTranslationData(); + return data.values[index]; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/data-path-element-data-query.function.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/data-path-element-data-query.function.ts index 517e393897..1d624a3123 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/data-path-element-data-query.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/data-path-element-data-query.function.ts @@ -2,14 +2,14 @@ import type { UmbBlockDataModel } from '../types.js'; /** * Validation Data Path Query generator for Block Element Data. - * write a JSON-Path filter similar to `?(@.key = 'my-key://1234')` + * write a JSON-Path filter similar to `?(@.key == 'my-key://1234')` * @param key {string} - The key of the block Element data. * @param data {{key: string}} - A data object with the key property. * @returns */ export function UmbDataPathBlockElementDataQuery(data: Pick): string { // write a array of strings for each property, where alias must be present and culture and segment are optional - //const filters: Array = [`@.key = '${key}'`]; + //const filters: Array = [`@.key == '${key}'`]; //return `?(${filters.join(' && ')})`; - return `?(@.key = '${data.key}')`; + return `?(@.key == '${data.key}')`; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/index.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/index.ts index 331352a0d8..f2af316128 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/validation/index.ts @@ -1,2 +1,3 @@ -export * from './block-data-validation-path-translator.controller.js'; +export * from './block-element-values-validation-path-translator.controller.js'; +export * from './block-element-data-validation-path-translator.controller.js'; export * from './data-path-element-data-query.function.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-manager.ts index d2c642d652..bac6c7d5bd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-manager.ts @@ -1,11 +1,12 @@ -import type { UmbBlockDataModel, UmbBlockDataValueModel } from '../types.js'; +import type { UmbBlockDataModel, UmbBlockDataValueModel, UmbBlockLayoutBaseModel } from '../types.js'; +import { UmbBlockElementValuesDataValidationPathTranslator } from '../validation/block-element-values-validation-path-translator.controller.js'; import { UmbBlockElementPropertyDatasetContext } from './block-element-property-dataset.context.js'; +import type { UmbBlockWorkspaceContext } from './block-workspace.context.js'; import type { UmbContentTypeModel, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type'; import { UmbContentTypeStructureManager } from '@umbraco-cms/backoffice/content-type'; import { type Observable, UmbClassState, - UmbObjectState, appendToFrozenArray, mergeObservables, } from '@umbraco-cms/backoffice/observable-api'; @@ -14,21 +15,42 @@ import { type UmbClassInterface, UmbControllerBase } from '@umbraco-cms/backoffi import { UmbDocumentTypeDetailRepository } from '@umbraco-cms/backoffice/document-type'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import { UmbValidationController } from '@umbraco-cms/backoffice/validation'; +import { UmbElementWorkspaceDataManager, type UmbElementPropertyDataOwner } from '@umbraco-cms/backoffice/content'; +import { UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; -export class UmbBlockElementManager extends UmbControllerBase { +import { UmbDataTypeItemRepositoryManager } from '@umbraco-cms/backoffice/data-type'; + +export class UmbBlockElementManager + extends UmbControllerBase + implements UmbElementPropertyDataOwner +{ // - #data = new UmbObjectState(undefined); - readonly data = this.#data.asObservable(); + + readonly #data = new UmbElementWorkspaceDataManager(this); + //#data = new UmbObjectState(undefined); + readonly data = this.#data.current; #getDataPromise = new Promise((resolve) => { this.#getDataResolver = resolve; }); #getDataResolver!: () => void; + public readonly readOnlyState = new UmbReadOnlyVariantStateManager(this); + #variantId = new UmbClassState(undefined); readonly variantId = this.#variantId.asObservable(); - readonly unique = this.#data.asObservablePart((data) => data?.key); - readonly contentTypeId = this.#data.asObservablePart((data) => data?.contentTypeKey); + readonly name; + readonly getName; + readonly unique = this.#data.createObservablePartOfCurrent((data) => data?.key); + readonly contentTypeId = this.#data.createObservablePartOfCurrent((data) => data?.contentTypeKey); + + readonly values = this.#data.createObservablePartOfCurrent((data) => data?.values); + getValues() { + return this.#data.getCurrent()?.values; + } + + readonly #dataTypeItemManager = new UmbDataTypeItemRepositoryManager(this); + #dataTypeSchemaAliasMap = new Map(); readonly structure = new UmbContentTypeStructureManager( this, @@ -37,19 +59,47 @@ export class UmbBlockElementManager extends UmbControllerBase { readonly validation = new UmbValidationController(this); - constructor(host: UmbControllerHost, dataPathPropertyName: string) { + constructor(host: UmbBlockWorkspaceContext, dataPathPropertyName: string) { super(host); + // Ugly, but we just inherit these from the workspace context: [NL] + this.name = host.name; + this.getName = host.getName; + this.observe(this.contentTypeId, (id) => this.structure.loadType(id)); this.observe(this.unique, (key) => { if (key) { - this.validation.setDataPath('$.' + dataPathPropertyName + `[?(@.key = '${key}')]`); + this.validation.setDataPath('$.' + dataPathPropertyName + `[?(@.key == '${key}')]`); } }); + + this.observe( + this.structure.contentTypeDataTypeUniques, + (dataTypeUniques: Array) => { + this.#dataTypeItemManager.setUniques(dataTypeUniques); + }, + null, + ); + this.observe( + this.#dataTypeItemManager.items, + (dataTypes) => { + // Make a map of the data type unique and editorAlias: + this.#dataTypeSchemaAliasMap = new Map( + dataTypes.map((dataType) => { + return [dataType.unique, dataType.propertyEditorSchemaAlias]; + }), + ); + }, + null, + ); } - reset() { - this.#data.setValue(undefined); + public isLoaded() { + return this.#getDataPromise; + } + + resetState() { + this.#data.clear(); } setVariantId(variantId: UmbVariantId | undefined) { @@ -61,12 +111,13 @@ export class UmbBlockElementManager extends UmbControllerBase { // TODO: rename to currentData: setData(data: UmbBlockDataModel | undefined) { - this.#data.setValue(data); + this.#data.setPersisted(data); + this.#data.setCurrent(data); this.#getDataResolver(); } getData() { - return this.#data.getValue(); + return this.#data.getCurrent(); } getUnique() { @@ -100,107 +151,88 @@ export class UmbBlockElementManager extends UmbControllerBase { /** * @function propertyValueByAlias - * @param {string} propertyAlias - Property Alias to observe the value of. - * @returns {Promise | undefined>} - Promise which resolves to an Observable + * @param {string} propertyAlias - The alias of the property + * @param {UmbVariantId} variantId - The variant + * @returns {Promise | undefined>} - An observable for the value of the property * @description Get an Observable for the value of this property. */ async propertyValueByAlias( propertyAlias: string, + variantId?: UmbVariantId, ): Promise | undefined> { - await this.#getDataPromise; - - return mergeObservables( - [ - this.#data.asObservablePart((data) => data?.values?.filter((x) => x?.alias === propertyAlias)), - await this.propertyVariantId(propertyAlias), - ], - ([propertyValues, propertyVariantId]) => { - if (!propertyValues || !propertyVariantId) return; - - return propertyValues.find((x) => propertyVariantId.compare(x))?.value as PropertyValueType; - }, + return this.#data.createObservablePartOfCurrent( + (data) => + data?.values?.find((x) => x?.alias === propertyAlias && (variantId ? variantId.compare(x) : true)) + ?.value as PropertyValueType, ); } /** * Get the current value of the property with the given alias and variantId. * @param {string} alias - The alias of the property - * @returns {ReturnType} The value or undefined if not set or found. + * @param {UmbVariantId | undefined} variantId - The variant id of the property + * @returns {ReturnType | undefined} The value or undefined if not set or found. */ - async getPropertyValue(alias: string) { - await this.#getDataPromise; - const managerVariantId = this.#variantId.getValue(); - if (!managerVariantId) return; - const property = await this.structure.getPropertyStructureByAlias(alias); - if (!property) return; - const variantId = this.#createPropertyVariantId(property, managerVariantId); - const currentData = this.getData(); - if (!currentData) return; - const newDataSet = currentData.values?.find((x) => x.alias === alias && (variantId ? variantId.compare(x) : true)); - return newDataSet?.value as ReturnType; + getPropertyValue(alias: string, variantId?: UmbVariantId) { + const currentData = this.#data.getCurrent(); + if (currentData) { + const newDataSet = currentData.values?.find( + (x) => x.alias === alias && (variantId ? variantId.compare(x) : true), + ); + return newDataSet?.value as ReturnType; + } + return undefined; } - - /** - * @function setPropertyValue - * @param {string} alias - The alias of the property - * @param {unknown} value - value can be a promise resolving into the actual value or the raw value it self. - * @returns {Promise} - * @description Set the value of this property. - */ - async setPropertyValue(alias: string, value: ValueType) { + async setPropertyValue(alias: string, value: ValueType, variantId?: UmbVariantId) { this.initiatePropertyValueChange(); - await this.#getDataPromise; - const managerVariantId = this.#variantId.getValue(); - if (!managerVariantId) return; + variantId ??= UmbVariantId.CreateInvariant(); const property = await this.structure.getPropertyStructureByAlias(alias); - if (!property) return; - const variantId = this.#createPropertyVariantId(property, managerVariantId); - const entry = { ...variantId.toObject(), alias, value } as UmbBlockDataValueModel; + if (!property) { + throw new Error(`Property alias "${alias}" not found.`); + } + + const editorAlias = this.#dataTypeSchemaAliasMap.get(property.dataType.unique); + if (!editorAlias) { + throw new Error(`Editor Alias of "${property.dataType.unique}" not found.`); + } + + const entry = { ...variantId.toObject(), alias, editorAlias, value } as UmbBlockDataValueModel; + const currentData = this.getData(); if (currentData) { const values = appendToFrozenArray( currentData.values ?? [], entry, - (x) => x.alias === alias && variantId.compare(x), + (x) => x.alias === alias && variantId!.compare(x), ); - this.#data.update({ values }); + this.#data.updateCurrent({ values }); } this.finishPropertyValueChange(); } - #updateLock = 0; initiatePropertyValueChange() { - this.#updateLock++; - this.#data.mute(); - // TODO: When ready enable this code will enable handling a finish automatically by this implementation 'using myState.initiatePropertyValueChange()' (Relies on TS support of Using) [NL] - /*return { - [Symbol.dispose]: this.finishPropertyValueChange, - };*/ + this.#data.initiatePropertyValueChange(); } finishPropertyValueChange = () => { - this.#updateLock--; - this.#triggerPropertyValueChanges(); + this.#data.finishPropertyValueChange(); }; - #triggerPropertyValueChanges() { - if (this.#updateLock === 0) { - this.#data.unmute(); - } + + public createPropertyDatasetContext(host: UmbControllerHost, variantId: UmbVariantId) { + return new UmbBlockElementPropertyDatasetContext(host, this, variantId); } - public createPropertyDatasetContext(host: UmbControllerHost) { - return new UmbBlockElementPropertyDatasetContext(host, this, this.getVariantId()); - } - - public setup(host: UmbClassInterface) { - this.createPropertyDatasetContext(host); + public setup(host: UmbClassInterface, variantId: UmbVariantId) { + this.createPropertyDatasetContext(host, variantId); // Provide Validation Context for this view: this.validation.provideAt(host); + + // TODO: Implement ctrl alias. + new UmbBlockElementValuesDataValidationPathTranslator(host); } public override destroy(): void { - this.#data.destroy(); this.structure.destroy(); super.destroy(); } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-property-dataset.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-property-dataset.context.ts index 8daff7f3fb..34e17f069a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-property-dataset.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-element-property-dataset.context.ts @@ -1,89 +1,27 @@ -import { UMB_BLOCK_ELEMENT_PROPERTY_DATASET_CONTEXT } from './block-element-property-dataset.context-token.js'; import type { UmbBlockElementManager } from './block-element-manager.js'; -import { UMB_BLOCK_WORKSPACE_CONTEXT } from './block-workspace.context-token.js'; import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property'; -import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; -import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; -import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api'; -import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { UmbElementPropertyDatasetContext } from '@umbraco-cms/backoffice/content'; +import { createObservablePart } from '@umbraco-cms/backoffice/observable-api'; -export class UmbBlockElementPropertyDatasetContext extends UmbControllerBase implements UmbPropertyDatasetContext { - #elementManager: UmbBlockElementManager; - public getVariantId() { - return this.#elementManager.getVariantId(); - } - #variantId: UmbVariantId; - - #readOnly = new UmbBooleanState(false); - public readOnly = this.#readOnly.asObservable(); - - // default data: - - getEntityType() { - return this.#elementManager.getEntityType(); - } - getUnique() { - return this.#elementManager.getUnique(); - } - - getName(): string | undefined { - return 'TODO: get label'; - } - readonly name: Observable = 'TODO: get label observable' as any; +export class UmbBlockElementPropertyDatasetContext + extends UmbElementPropertyDatasetContext + implements UmbPropertyDatasetContext +{ + readonly name; + readonly culture; + readonly segment; + readonly getName; constructor(host: UmbControllerHost, elementManager: UmbBlockElementManager, variantId?: UmbVariantId) { // The controller alias, is a very generic name cause we want only one of these for this controller host. - super(host, UMB_PROPERTY_DATASET_CONTEXT.toString()); - this.#elementManager = elementManager; - this.#variantId = variantId ?? UmbVariantId.CreateInvariant(); + super(host, elementManager, variantId); - this.consumeContext(UMB_BLOCK_WORKSPACE_CONTEXT, (workspace) => { - this.observe( - workspace.readOnlyState.states, - (states) => { - const isReadOnly = states.some((state) => state.variantId.equal(this.#variantId)); - this.#readOnly.setValue(isReadOnly); - }, - 'umbObserveReadOnlyStates', - ); - }); - - this.provideContext(UMB_BLOCK_ELEMENT_PROPERTY_DATASET_CONTEXT, this); - } - - propertyVariantId?(propertyAlias: string): Promise> { - return this.#elementManager.propertyVariantId(propertyAlias); - } - - /** - * @function propertyValueByAlias - * @param {string} propertyAlias - * @returns {Promise | undefined>} - * @description Get an Observable for the value of this property. - */ - async propertyValueByAlias(propertyAlias: string) { - return await this.#elementManager.propertyValueByAlias(propertyAlias); - } - - /** - * @function setPropertyValue - * @param {string} alias - * @param {unknown} value - value can be a promise resolving into the actual value or the raw value it self. - * @returns {Promise} - * @description Set the value of this property. - */ - async setPropertyValue(alias: string, value: PromiseLike) { - this.#elementManager.setPropertyValue(alias, value); - } - - /** - * Gets the read-only state of the current variant culture. - * @returns {*} {boolean} - * @memberof UmbBlockGridInlinePropertyDatasetContext - */ - getReadOnly(): boolean { - return this.#readOnly.getValue(); + // Ugly, but we just inherit these from the workspace context: [NL] + this.name = elementManager.name; + this.getName = elementManager.getName; + this.culture = createObservablePart(elementManager.variantId, (v) => v?.culture); + this.segment = createObservablePart(elementManager.variantId, (v) => v?.segment); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts index fa1a5c4f08..31cf151c32 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/block-workspace.context.ts @@ -23,6 +23,7 @@ import { UMB_BLOCK_MANAGER_CONTEXT, type UmbBlockWorkspaceOriginData, type UmbBlockWorkspaceData, + UMB_BLOCK_ENTRY_CONTEXT, } from '@umbraco-cms/backoffice/block'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; @@ -59,9 +60,8 @@ export class UmbBlockWorkspaceContext(undefined); - readonly name = this.#label.asObservable(); + #name = new UmbStringState(undefined); + readonly name = this.#name.asObservable(); #variantId = new UmbClassState(undefined); readonly variantId = this.#variantId.asObservable(); @@ -90,7 +90,7 @@ export class UmbBlockWorkspaceContext { - this.#liveEditingMode = liveEditingMode; + this.#liveEditingMode = liveEditingMode ?? false; }, 'observeLiveEditingMode', ); @@ -116,6 +116,7 @@ export class UmbBlockWorkspaceContext { @@ -152,12 +153,16 @@ export class UmbBlockWorkspaceContext { this.#blockEntries = context; }).asPromise(); + this.consumeContext(UMB_BLOCK_ENTRY_CONTEXT, (context) => { + this.#name.setValue(context.getName()); + }); + this.observe(this.variantId, (variantId) => { this.content.setVariantId(variantId); this.settings.setVariantId(variantId); @@ -194,13 +199,13 @@ export class UmbBlockWorkspaceContext { if (layoutData) { - this.#blockManager?.setOneLayout(layoutData); + this.#blockManager?.setOneLayout( + layoutData, + this.#modalContext?.data.originData as UmbBlockWorkspaceOriginData, + ); } }, 'observeThisLayout', @@ -443,7 +451,7 @@ export class UmbBlockWorkspaceContext = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_BLOCK_WORKSPACE_ALIAS, }, { @@ -37,7 +38,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_BLOCK_WORKSPACE_ALIAS, }, { @@ -69,7 +70,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_BLOCK_WORKSPACE_ALIAS, }, ], @@ -94,7 +95,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_BLOCK_WORKSPACE_ALIAS, }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-content-no-router.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-content-no-router.element.ts index 29a1e01e71..652400020c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-content-no-router.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-content-no-router.element.ts @@ -6,7 +6,6 @@ import { UmbContentTypeContainerStructureHelper } from '@umbraco-cms/backoffice/ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UmbPropertyTypeContainerModel } from '@umbraco-cms/backoffice/content-type'; import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/workspace'; -import { UmbLanguageItemRepository } from '@umbraco-cms/backoffice/language'; /** * @element umb-block-workspace-view-edit-content-no-router @@ -29,15 +28,6 @@ export class UmbBlockWorkspaceViewEditContentNoRouterElement extends UmbLitEleme //@state() //private _activeTabName?: string | null | undefined; - @state() - private _ownerContentTypeName?: string; - - @state() - private _variantName?: string; - - @state() - private _exposed?: boolean; - #blockWorkspace?: typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE; #tabsStructureHelper = new UmbContentTypeContainerStructureHelper(this); @@ -57,41 +47,7 @@ export class UmbBlockWorkspaceViewEditContentNoRouterElement extends UmbLitEleme this.#blockWorkspace = context; this.#tabsStructureHelper.setStructureManager(context.content.structure); - context.content.setup(this); this.#observeRootGroups(); - - this.observe( - context.content.structure.ownerContentTypeName, - (name) => { - this._ownerContentTypeName = name; - }, - 'observeContentTypeName', - ); - - this.observe( - context.variantId, - async (variantId) => { - if (variantId) { - // TODO: Support segment name? - const culture = variantId.culture; - if (culture) { - const languageRepository = new UmbLanguageItemRepository(this); - const { data } = await languageRepository.requestItems([culture]); - const name = data?.[0].name; - this._variantName = name ? this.localize.string(name) : undefined; - } - } - }, - 'observeVariant', - ); - - this.observe( - context.exposed, - (exposed) => { - this._exposed = exposed; - }, - 'observeExposed', - ); }); } @@ -128,19 +84,7 @@ export class UmbBlockWorkspaceViewEditContentNoRouterElement extends UmbLitEleme this._activeTabId = tabId; } - #expose = () => { - this.#blockWorkspace?.expose(); - }; - override render() { - if (this._exposed === false) { - return html` - `; - } if (!this._tabs) return; return html` ${this._tabs.length > 1 || (this._tabs.length === 1 && this._hasRootGroups) diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts index cd570f360d..1b1acc7425 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit-properties.element.ts @@ -5,6 +5,8 @@ import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import type { UmbContentTypeModel, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type'; import { UmbContentTypePropertyStructureHelper } from '@umbraco-cms/backoffice/content-type'; import { UmbLitElement, umbDestroyOnDisconnect } from '@umbraco-cms/backoffice/lit-element'; +import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { UmbDataPathPropertyValueQuery } from '@umbraco-cms/backoffice/validation'; @customElement('umb-block-workspace-view-edit-properties') export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement { @@ -38,12 +40,22 @@ export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement { @state() private _ownerEntityType?: string; + #variantId?: UmbVariantId; + constructor() { super(); this.consumeContext(UMB_BLOCK_WORKSPACE_CONTEXT, (workspaceContext) => { this.#blockWorkspace = workspaceContext; this._ownerEntityType = this.#blockWorkspace.getEntityType(); + this.observe( + workspaceContext.variantId, + (variantId) => { + this.#variantId = variantId; + this.#generatePropertyDataPath(); + }, + 'observeVariantId', + ); this.#setStructureManager(); }); } @@ -61,10 +73,24 @@ export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement { ); } + /* #generatePropertyDataPath() { if (!this._propertyStructure) return; this._dataPaths = this._propertyStructure.map((property) => `$.${property.alias}`); } + */ + + #generatePropertyDataPath() { + if (!this.#variantId || !this._propertyStructure) return; + this._dataPaths = this._propertyStructure.map( + (property) => + `$.values[${UmbDataPathPropertyValueQuery({ + alias: property.alias, + culture: property.variesByCulture ? this.#variantId!.culture : null, + segment: property.variesBySegment ? this.#variantId!.segment : null, + })}].value`, + ); + } override render() { return repeat( diff --git a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit.element.ts b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit.element.ts index 3b5f9c6fda..21205661e5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/block/block/workspace/views/edit/block-workspace-view-edit.element.ts @@ -69,8 +69,16 @@ export class UmbBlockWorkspaceViewEditElement extends UmbLitElement implements U const blockManager = this.#blockWorkspace[this.#managerName]; this.#tabsStructureHelper.setStructureManager(blockManager.structure); - // Create Data Set & setup Validation Context: - blockManager.setup(this); + this.observe( + this.#blockWorkspace.variantId, + (variantId) => { + if (variantId) { + // Create Data Set & setup Validation Context: + blockManager.setup(this, variantId); + } + }, + 'observeVariantId', + ); this.observe( await this.#blockWorkspace![this.#managerName!].structure.hasRootContainers('Group'), diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/action/create/collection-create-action.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/action/create/collection-create-action.element.ts new file mode 100644 index 0000000000..fd80de09a2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/action/create/collection-create-action.element.ts @@ -0,0 +1,157 @@ +import { html, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import type { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api'; +import { UmbExtensionsApiInitializer } from '@umbraco-cms/backoffice/extension-api'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity'; +import type { ManifestEntityCreateOptionAction } from '@umbraco-cms/backoffice/entity-create-option-action'; + +type ManifestType = ManifestEntityCreateOptionAction; + +const elementName = 'umb-collection-create-action-button'; +@customElement(elementName) +export class UmbCollectionCreateActionButtonElement extends UmbLitElement { + @state() + private _popoverOpen = false; + + @state() + private _multipleOptions = false; + + @state() + private _apiControllers: Array> = []; + + @state() + _hrefList: Array = []; + + #createLabel = this.localize.term('general_create'); + #entityContext?: typeof UMB_ENTITY_CONTEXT.TYPE; + + #onPopoverToggle(event: PointerEvent) { + // TODO: This ignorer is just neede for JSON SCHEMA TO WORK, As its not updated with latest TS jet. + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this._popoverOpen = event.newState === 'open'; + } + + async #onClick(event: Event, controller: UmbExtensionApiInitializer, href?: string) { + event.stopPropagation(); + + // skip if href is defined + if (href) { + return; + } + + if (!controller.api) throw new Error('No API found'); + await controller.api.execute(); + } + + constructor() { + super(); + + this.consumeContext(UMB_ENTITY_CONTEXT, (context) => { + this.#entityContext = context; + this.#initApi(); + }); + } + + #initApi() { + if (!this.#entityContext) return; + + const entityType = this.#entityContext.getEntityType(); + if (!entityType) throw new Error('No entityType found'); + + const unique = this.#entityContext.getUnique(); + if (unique === undefined) throw new Error('No unique found'); + + new UmbExtensionsApiInitializer( + this, + umbExtensionsRegistry, + 'entityCreateOptionAction', + (manifest: ManifestType) => { + return [{ entityType, unique, meta: manifest.meta }]; + }, + (manifest: ManifestType) => manifest.forEntityTypes.includes(entityType), + async (controllers) => { + this._apiControllers = controllers as unknown as Array>; + this._multipleOptions = controllers.length > 1; + const hrefPromises = this._apiControllers.map((controller) => controller.api?.getHref()); + this._hrefList = await Promise.all(hrefPromises); + }, + ); + } + + #getTarget(href?: string) { + if (href && href.startsWith('http')) { + return '_blank'; + } + + return '_self'; + } + + override render() { + return this._multipleOptions ? this.#renderMultiOptionAction() : this.#renderSingleOptionAction(); + } + + #renderSingleOptionAction() { + return html` this.#onClick(event, this._apiControllers[0])}>`; + } + + #renderMultiOptionAction() { + return html` + + ${this.#createLabel} + + + ${this.#renderDropdown()} + `; + } + + #renderDropdown() { + return html` + + + + ${this._apiControllers.map((controller, index) => this.#renderMenuItem(controller, index))} + + + + `; + } + + #renderMenuItem(controller: UmbExtensionApiInitializer, index: number) { + const manifest = controller.manifest; + if (!manifest) throw new Error('No manifest found'); + + const label = manifest.meta.label ? this.localize.string(manifest.meta.label) : manifest.name; + const href = this._hrefList[index]; + + return html` + this.#onClick(event, controller, href)} + href=${ifDefined(href)} + target=${this.#getTarget(href)}> + + + `; + } +} + +export { UmbCollectionCreateActionButtonElement as element }; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: UmbCollectionCreateActionButtonElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/action/create/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/action/create/manifests.ts new file mode 100644 index 0000000000..429fb41b26 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/action/create/manifests.ts @@ -0,0 +1,19 @@ +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'kind', + alias: 'Umb.Kind.CollectionAction.Create', + matchKind: 'create', + matchType: 'collectionAction', + manifest: { + type: 'collectionAction', + kind: 'create', + element: () => import('./collection-create-action.element.js'), + weight: 1200, + meta: { + label: '#actions_create', + }, + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/action/create/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/action/create/types.ts new file mode 100644 index 0000000000..50ef773e2c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/action/create/types.ts @@ -0,0 +1,12 @@ +import type { ManifestCollectionAction } from '../../extensions/index.js'; + +export interface ManifestCollectionActionCreateKind extends ManifestCollectionAction { + type: 'collectionAction'; + kind: 'create'; +} + +declare global { + interface UmbExtensionManifestMap { + umbCollectionActionCreateKind: ManifestCollectionActionCreateKind; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/action/manifests.ts new file mode 100644 index 0000000000..19f4780244 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/action/manifests.ts @@ -0,0 +1,4 @@ +import { manifests as createManifests } from './create/manifests.js'; +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [...createManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-alias.manifest.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-alias.manifest.ts deleted file mode 100644 index 52eadd23f3..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-alias.manifest.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { ManifestCondition, UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; - -export type CollectionAliasConditionConfig = UmbConditionConfigBase & { - /** - * The collection that this extension should be available in - * @example - * "Umb.Collection.User" - */ - match: string; -}; - -export const UMB_COLLECTION_ALIAS_CONDITION = 'Umb.Condition.CollectionAlias'; -export const manifest: ManifestCondition = { - type: 'condition', - name: 'Collection Alias Condition', - alias: UMB_COLLECTION_ALIAS_CONDITION, - api: () => import('./collection-alias.condition.js'), -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-bulk-action-permission.manifest.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-bulk-action-permission.manifest.ts deleted file mode 100644 index 17fb2ba24c..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-bulk-action-permission.manifest.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { UmbCollectionBulkActionPermissions } from './types.js'; -import type { ManifestCondition, UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; - -export type CollectionBulkActionPermissionConditionConfig = UmbConditionConfigBase< - typeof UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION -> & { - match: (permissions: UmbCollectionBulkActionPermissions) => boolean; -}; - -export const UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION = 'Umb.Condition.CollectionBulkActionPermission'; - -export const manifest: ManifestCondition = { - type: 'condition', - name: 'Collection Bulk Action Permission Condition', - alias: UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION, - api: () => import('./collection-bulk-action-permission.condition.js'), -}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-action-bundle.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-action-bundle.element.ts index 40bcbe93b6..eb205435e5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-action-bundle.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-action-bundle.element.ts @@ -1,4 +1,4 @@ -import { html, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { css, customElement, html } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; @customElement('umb-collection-action-bundle') @@ -6,6 +6,14 @@ export class UmbCollectionActionBundleElement extends UmbLitElement { override render() { return html``; } + + static override readonly styles = [ + css` + :host { + display: contents; + } + `, + ]; } declare global { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-filter-field.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-filter-field.element.ts new file mode 100644 index 0000000000..7f1d3f9f8b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-filter-field.element.ts @@ -0,0 +1,47 @@ +import { UMB_COLLECTION_CONTEXT } from '../default/index.js'; +import { css, customElement, html } from '@umbraco-cms/backoffice/external/lit'; +import { debounce } from '@umbraco-cms/backoffice/utils'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +@customElement('umb-collection-filter-field') +export class UmbCollectionFilterFieldElement extends UmbLitElement { + #collectionContext?: typeof UMB_COLLECTION_CONTEXT.TYPE; + + constructor() { + super(); + + this.consumeContext(UMB_COLLECTION_CONTEXT, (context) => { + this.#collectionContext = context; + }); + } + + #debouncedFilter = debounce((filter: string) => this.#collectionContext?.setFilter({ filter }), 500); + + #onInput(event: InputEvent & { target: HTMLInputElement }) { + const filter = event.target.value ?? ''; + this.#debouncedFilter(filter); + } + + override render() { + return html` + + `; + } + + static override readonly styles = [ + css` + uui-input { + display: block; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-collection-filter-field': UmbCollectionFilterFieldElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-view-bundle.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-view-bundle.element.ts index ff873f9671..d26d5aaccd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-view-bundle.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/collection-view-bundle.element.ts @@ -1,8 +1,7 @@ -import type { UmbDefaultCollectionContext } from '../default/index.js'; import { UMB_COLLECTION_CONTEXT } from '../default/index.js'; -import type { UmbCollectionLayoutConfiguration } from '../types.js'; import type { ManifestCollectionView } from '../extensions/index.js'; -import { css, html, customElement, state, nothing, repeat, query } from '@umbraco-cms/backoffice/external/lit'; +import type { UmbCollectionLayoutConfiguration } from '../types.js'; +import { css, customElement, html, nothing, query, repeat, state } from '@umbraco-cms/backoffice/external/lit'; import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; @@ -30,7 +29,7 @@ export class UmbCollectionViewBundleElement extends UmbLitElement { @state() private _entityUnique?: string; - #collectionContext?: UmbDefaultCollectionContext; + #collectionContext?: typeof UMB_COLLECTION_CONTEXT.TYPE; constructor() { super(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/index.ts index 4b753400bf..7abcffff90 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/components/index.ts @@ -1,11 +1,13 @@ -import './pagination/collection-pagination.element.js'; +import './collection-action-bundle.element.js'; +import './collection-filter-field.element.js'; import './collection-selection-actions.element.js'; import './collection-toolbar.element.js'; import './collection-view-bundle.element.js'; -import './collection-action-bundle.element.js'; +import './pagination/collection-pagination.element.js'; -export * from './pagination/collection-pagination.element.js'; +export * from './collection-action-bundle.element.js'; +export * from './collection-filter-field.element.js'; export * from './collection-selection-actions.element.js'; export * from './collection-toolbar.element.js'; export * from './collection-view-bundle.element.js'; -export * from './collection-action-bundle.element.js'; +export * from './pagination/collection-pagination.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-alias.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/collection-alias.condition.ts similarity index 83% rename from src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-alias.condition.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/collection-alias.condition.ts index 166d81e131..6a4a03f6e0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-alias.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/collection-alias.condition.ts @@ -1,5 +1,5 @@ -import { UMB_COLLECTION_CONTEXT } from './default/index.js'; -import type { CollectionAliasConditionConfig } from './collection-alias.manifest.js'; +import { UMB_COLLECTION_CONTEXT } from '../default/index.js'; +import type { CollectionAliasConditionConfig } from './types.js'; import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api'; import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-bulk-action-permission.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/collection-bulk-action-permission.condition.ts similarity index 89% rename from src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-bulk-action-permission.condition.ts rename to src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/collection-bulk-action-permission.condition.ts index 3b13ad47e2..885ad3f982 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-bulk-action-permission.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/collection-bulk-action-permission.condition.ts @@ -1,5 +1,5 @@ -import { UMB_COLLECTION_CONTEXT } from './default/index.js'; -import type { CollectionBulkActionPermissionConditionConfig } from './collection-bulk-action-permission.manifest.js'; +import { UMB_COLLECTION_CONTEXT } from '../default/index.js'; +import type { CollectionBulkActionPermissionConditionConfig } from './types.js'; import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api'; import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/constants.ts new file mode 100644 index 0000000000..36fa086ad7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/constants.ts @@ -0,0 +1,3 @@ +export const UMB_COLLECTION_ALIAS_CONDITION = 'Umb.Condition.CollectionAlias'; + +export const UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION = 'Umb.Condition.CollectionBulkActionPermission'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/index.ts new file mode 100644 index 0000000000..83279e8836 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/index.ts @@ -0,0 +1,4 @@ +export * from './collection-bulk-action-permission.condition.js'; +export * from './collection-alias.condition.js'; +export * from './constants.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/manifests.ts new file mode 100644 index 0000000000..3e4eadfc62 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/manifests.ts @@ -0,0 +1,17 @@ +import { UMB_COLLECTION_ALIAS_CONDITION, UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION } from './constants.js'; +import type { ManifestCondition } from '@umbraco-cms/backoffice/extension-api'; + +export const manifests: Array = [ + { + type: 'condition', + name: 'Collection Alias Condition', + alias: UMB_COLLECTION_ALIAS_CONDITION, + api: () => import('./collection-alias.condition.js'), + }, + { + type: 'condition', + name: 'Collection Bulk Action Permission Condition', + alias: UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION, + api: () => import('./collection-bulk-action-permission.condition.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/types.ts new file mode 100644 index 0000000000..6a19e0a3c9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/conditions/types.ts @@ -0,0 +1,25 @@ +import type { UmbCollectionBulkActionPermissions } from '../types.js'; +import type { UMB_COLLECTION_ALIAS_CONDITION, UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION } from './constants.js'; +import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; + +export type CollectionBulkActionPermissionConditionConfig = UmbConditionConfigBase< + typeof UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION +> & { + match: (permissions: UmbCollectionBulkActionPermissions) => boolean; +}; + +export type CollectionAliasConditionConfig = UmbConditionConfigBase & { + /** + * The collection that this extension should be available in + * @example + * "Umb.Collection.User" + */ + match: string; +}; + +declare global { + interface UmbExtensionConditionConfigMap { + CollectionBulkActionPermissionConditionConfig: CollectionBulkActionPermissionConditionConfig; + CollectionAliasConditionConfig: CollectionAliasConditionConfig; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/index.ts index ba4eedfb8a..ca9701d258 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/index.ts @@ -5,15 +5,13 @@ import './components/index.js'; export * from './default/collection-default.element.js'; export * from './collection.element.js'; export * from './components/index.js'; +export * from './conditions/index.js'; export * from './collection-item-picker-modal/index.js'; export * from './default/collection-default.context.js'; export * from './default/collection-default.context-token.js'; export * from './collection-filter-model.interface.js'; -export * from './types.js'; - -export { UMB_COLLECTION_ALIAS_CONDITION } from './collection-alias.manifest.js'; -export { UMB_COLLECTION_BULK_ACTION_PERMISSION_CONDITION } from './collection-bulk-action-permission.manifest.js'; +export type * from './types.js'; export { UmbCollectionActionElement, UmbCollectionActionBase } from './action/index.js'; export type { UmbCollectionDataSource, UmbCollectionRepository } from './repository/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/manifests.ts index 1ee0674ee8..c59c5fa9f4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/manifests.ts @@ -1,10 +1,10 @@ import type { UmbExtensionManifestKind } from '../extension-registry/registry.js'; -import { manifest as collectionAliasCondition } from './collection-alias.manifest.js'; -import { manifest as collectionBulkActionPermissionCondition } from './collection-bulk-action-permission.manifest.js'; +import { manifests as conditionManifests } from './conditions/manifests.js'; +import { manifests as actionManifests } from './action/manifests.js'; import { manifests as workspaceViewManifests } from './workspace-view/manifests.js'; export const manifests: Array = [ + ...actionManifests, ...workspaceViewManifests, - collectionAliasCondition, - collectionBulkActionPermissionCondition, + ...conditionManifests, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/types.ts index 153dae2e3d..352a8176c7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/types.ts @@ -3,6 +3,7 @@ import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import type { UmbPaginationManager } from '@umbraco-cms/backoffice/utils'; export type * from './extensions/index.js'; +export type * from './conditions/types.js'; export interface UmbCollectionBulkActionPermissions { allowBulkCopy: boolean; @@ -48,4 +49,4 @@ export interface UmbCollectionContext { totalItems: Observable; } -export * from './extensions/index.js'; +export type * from './extensions/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/history/history-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/history/history-item.element.ts index 92671b347c..a5814884c3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/history/history-item.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/history/history-item.element.ts @@ -63,6 +63,7 @@ export class UmbHistoryItemElement extends UmbLitElement { .user-info div { display: flex; flex-direction: column; + min-width: var(--uui-size-60); } .detail { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/index.ts index 03701d17e5..79f883296b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/index.ts @@ -1,7 +1,7 @@ export * from './components/index.js'; +export * from './composition/index.js'; export * from './modals/index.js'; export * from './repository/index.js'; export * from './structure/index.js'; -export * from './types.js'; export * from './workspace/index.js'; -export * from './composition/index.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/structure/content-type-structure-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/structure/content-type-structure-manager.class.ts index 2cc6b7187f..eb0826f824 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/structure/content-type-structure-manager.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/structure/content-type-structure-manager.class.ts @@ -67,9 +67,9 @@ export class UmbContentTypeStructureManager< // Notice this may need to use getValue to avoid resetting it self. [NL] return contentTypes.some((x) => x.properties.length > 0); }); - readonly contentTypePropertyAliases = this.#contentTypes.asObservablePart((contentTypes) => { - return contentTypes.flatMap((x) => x.properties ?? []).map((x) => x.alias); - }); + readonly contentTypePropertyAliases = createObservablePart(this.contentTypeProperties, (properties) => + properties.map((x) => x.alias), + ); readonly contentTypeUniques = this.#contentTypes.asObservablePart((x) => x.map((y) => y.unique)); readonly contentTypeAliases = this.#contentTypes.asObservablePart((x) => x.map((y) => y.alias)); @@ -105,6 +105,8 @@ export class UmbContentTypeStructureManager< * @returns {Promise} - Promise resolved */ public async loadType(unique?: string) { + //if (!unique) return; + //if (this.#ownerContentTypeUnique === unique) return; this._reset(); this.#ownerContentTypeUnique = unique; @@ -724,6 +726,7 @@ export class UmbContentTypeStructureManager< } private _reset() { + this.#contentTypes.setValue([]); this.#contentTypeObservers.forEach((observer) => observer.destroy()); this.#contentTypeObservers = []; this.#contentTypes.setValue([]); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/types.ts index 132af25ee5..3d76913002 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/types.ts @@ -1,6 +1,8 @@ import type { CompositionTypeModel } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models'; +export type * from './composition/types.js'; + export type UmbPropertyContainerTypes = 'Group' | 'Tab'; export interface UmbPropertyTypeContainerModel { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts index 31059a012f..ead29d145b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-group.element.ts @@ -228,7 +228,7 @@ export class UmbContentTypeWorkspaceViewEditGroupElement extends UmbLitElement { opacity: 0.5; } - :host([drag-placeholder]) > * { + :host([drag-placeholder]) > uui-box > * { visibility: hidden; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts index b34a765d96..02374f4f8a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor-property.element.ts @@ -414,7 +414,6 @@ export class UmbContentTypeDesignEditorPropertyElement extends UmbLitElement { position: sticky; top: var(--uui-size-space-4); height: min-content; - z-index: 2; } #editor { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts index 9ab77fca89..9105d91fd0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content-type/workspace/views/design/content-type-design-editor.element.ts @@ -478,7 +478,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements const ownedTab = this.#tabsStructureHelper.isOwnerChildContainer(tab.id!) ?? false; return html` ${ownedTab - ? html` ${tab.name!} + ? html` ${tabName} this.#tabNameChanged(e, tab)} @input=${(e: InputEvent) => this.#tabNameChanged(e, tab)} @blur=${(e: FocusEvent) => this.#tabNameBlur(e, tab)}> @@ -521,7 +524,11 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements } if (ownedTab) { - return html`
${tab.name!} ${this.renderDeleteFor(tab)}
`; + return html`
+ ${hasTabName ? tab.name : 'Unnamed'} ${this.renderDeleteFor( + tab, + )} +
`; } else { return html`
${tab.name!}
`; } @@ -630,6 +637,10 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements gap: var(--uui-size-space-3); } + .invaild { + color: var(--uui-color-danger, #d42054); + } + .trash { opacity: 1; transition: opacity 100ms; @@ -640,7 +651,7 @@ export class UmbContentTypeDesignEditorElement extends UmbLitElement implements transition: opacity 100ms; } - uui-input:not(:focus, :hover) { + uui-input:not(:focus, :hover, :invalid) { border: 1px solid transparent; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/controller/merge-content-variant-data.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/controller/merge-content-variant-data.controller.ts index e4ecfc2c04..aa50029af8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content/controller/merge-content-variant-data.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/controller/merge-content-variant-data.controller.ts @@ -1,5 +1,5 @@ import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; -import type { UmbContentDetailModel, UmbPotentialContentValueModel } from '@umbraco-cms/backoffice/content'; +import type { UmbContentLikeDetailModel, UmbPotentialContentValueModel } from '@umbraco-cms/backoffice/content'; import { createExtensionApi } from '@umbraco-cms/backoffice/extension-api'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbVariantId, type UmbVariantDataModel } from '@umbraco-cms/backoffice/variant'; @@ -17,13 +17,13 @@ function defaultCompareVariantMethod(a: UmbVariantDataModel, b: UmbVariantDataMo export class UmbMergeContentVariantDataController extends UmbControllerBase { /** * Merges content variant data based on selected variants and variants to store. - * @param {UmbContentDetailModel | undefined} persistedData - The persisted content variant data. - * @param {UmbContentDetailModel} currentData - The current content variant data. + * @param {UmbContentLikeDetailModel | undefined} persistedData - The persisted content variant data. + * @param {UmbContentLikeDetailModel} currentData - The current content variant data. * @param {Array} selectedVariants - The selected variants. * @param {Array} variantsToStore - The variants to store, we sometimes have additional variants that we like to process. This is typically the invariant variant, which we do not want to have as part of the variants data therefore a difference. - * @returns {Promise} - A promise that resolves to the merged content variant data. + * @returns {Promise} - A promise that resolves to the merged content variant data. */ - async process( + async process( persistedData: ModelType | undefined, currentData: ModelType, selectedVariants: Array, @@ -39,14 +39,17 @@ export class UmbMergeContentVariantDataController extends UmbControllerBase { currentData.values, variantsToStore, ), + }; + + if (currentData.variants) { // Notice for variants we do not want to include all the variants that we are processing. but just the once selected for the process. (Not include invariant if we are in a variant document) [NL] - variants: this.#processVariants( + result.variants = this.#processVariants( persistedData?.variants, currentData.variants, selectedVariants, defaultCompareVariantMethod, - ), - }; + ); + } this.destroy(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/index.ts index 0a951a2634..1fe642380a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/index.ts @@ -1,9 +1,9 @@ export { UMB_CONTENT_PROPERTY_CONTEXT } from './content-property.context-token.js'; export { UmbContentPropertyContext } from './content-property.context.js'; -export * from './manager/content-data-manager.js'; -export * from './controller/merge-content-variant-data.controller.js'; -export * from './property-dataset-context/content-property-dataset.context.js'; -export * from './workspace/index.js'; export * from './collection/index.js'; export * from './constants.js'; +export * from './controller/merge-content-variant-data.controller.js'; +export * from './manager/index.js'; +export * from './property-dataset-context/index.js'; +export * from './workspace/index.js'; export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/manager/content-data-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/manager/content-data-manager.ts index e9cad5be02..3c6601037f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content/manager/content-data-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/manager/content-data-manager.ts @@ -1,63 +1,24 @@ -import { UmbMergeContentVariantDataController } from '../controller/merge-content-variant-data.controller.js'; +import { UmbElementWorkspaceDataManager } from './element-data-manager.js'; import type { UmbContentDetailModel } from '@umbraco-cms/backoffice/content'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { appendToFrozenArray, jsonStringComparison } from '@umbraco-cms/backoffice/observable-api'; import { UmbVariantId, type UmbEntityVariantModel } from '@umbraco-cms/backoffice/variant'; -import { UmbEntityWorkspaceDataManager, type UmbWorkspaceDataManager } from '@umbraco-cms/backoffice/workspace'; export class UmbContentWorkspaceDataManager< - ModelType extends UmbContentDetailModel, - ModelVariantType extends UmbEntityVariantModel = ModelType extends { variants: UmbEntityVariantModel[] } - ? ModelType['variants'][0] - : never, - > - extends UmbEntityWorkspaceDataManager - implements UmbWorkspaceDataManager -{ + ModelType extends UmbContentDetailModel, + ModelVariantType extends UmbEntityVariantModel = ModelType extends { variants: UmbEntityVariantModel[] } + ? ModelType['variants'][0] + : never, +> extends UmbElementWorkspaceDataManager { // //#repository; #variantScaffold?: ModelVariantType; - #varies?: boolean; - //#variesByCulture?: boolean; - //#variesBySegment?: boolean; - constructor(host: UmbControllerHost, variantScaffold: ModelVariantType) { super(host); this.#variantScaffold = variantScaffold; } - #updateLock = 0; - initiatePropertyValueChange() { - this.#updateLock++; - this._current.mute(); - // TODO: When ready enable this code will enable handling a finish automatically by this implementation 'using myState.initiatePropertyValueChange()' (Relies on TS support of Using) [NL] - /*return { - [Symbol.dispose]: this.finishPropertyValueChange, - };*/ - } - finishPropertyValueChange = () => { - this.#updateLock--; - this.#triggerPropertyValueChanges(); - }; - #triggerPropertyValueChanges() { - if (this.#updateLock === 0) { - this._current.unmute(); - } - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - setVariesByCulture(vary: boolean | undefined) { - //this.#variesByCulture = vary; - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - setVariesBySegment(vary: boolean | undefined) { - //this.#variesBySegment = vary; - } - setVaries(vary: boolean | undefined) { - this.#varies = vary; - } - ensureVariantData(variantId: UmbVariantId) { this.updateVariantData(variantId); } @@ -66,7 +27,7 @@ export class UmbContentWorkspaceDataManager< const currentData = this.getCurrent(); if (!currentData) throw new Error('Data is missing'); if (!this.#variantScaffold) throw new Error('Variant scaffold data is missing'); - if (this.#varies === true) { + if (this._varies === true) { // If variant Id is invariant, we don't to have the variant appended to our data. if (variantId.isInvariant()) return; const variant = currentData.variants.find((x) => variantId.compare(x)); @@ -82,7 +43,7 @@ export class UmbContentWorkspaceDataManager< ) as Array; // TODO: I have some trouble with TypeScript here, I does not look like me, but i had to give up. [NL] this._current.update({ variants: newVariants } as any); - } else if (this.#varies === false) { + } else if (this._varies === false) { // TODO: Beware about segments, in this case we need to also consider segments, if its allowed to vary by segments. const invariantVariantId = UmbVariantId.CreateInvariant(); const variant = currentData.variants.find((x) => invariantVariantId.compare(x)); @@ -102,32 +63,6 @@ export class UmbContentWorkspaceDataManager< } } - async constructData(selectedVariants: Array): Promise { - // Lets correct the selected variants, so invariant is included, or the only one if invariant. - // TODO: VDIVD: Could a document be set to invariant but hold variant data inside it? - const invariantVariantId = UmbVariantId.CreateInvariant(); - let variantsToStore = [invariantVariantId]; - if (this.#varies === false) { - // If we do not vary, we wil just pick the invariant variant id. - selectedVariants = [invariantVariantId]; - } else { - variantsToStore = [...selectedVariants, invariantVariantId]; - } - - const data = this._current.getValue(); - if (!data) throw new Error('Current data is missing'); - if (!data.unique) throw new Error('Unique of current data is missing'); - - const persistedData = this.getPersisted(); - - return await new UmbMergeContentVariantDataController(this).process( - persistedData, - data, - selectedVariants, - variantsToStore, - ); - } - getChangedVariants() { const persisted = this.getPersisted(); const current = this.getCurrent(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/manager/element-data-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/manager/element-data-manager.ts new file mode 100644 index 0000000000..7dd3ddc91e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/manager/element-data-manager.ts @@ -0,0 +1,70 @@ +import { UmbMergeContentVariantDataController } from '../controller/merge-content-variant-data.controller.js'; +import type { UmbElementDetailModel } from '@umbraco-cms/backoffice/content'; +import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import { UmbEntityWorkspaceDataManager, type UmbWorkspaceDataManager } from '@umbraco-cms/backoffice/workspace'; + +export class UmbElementWorkspaceDataManager + extends UmbEntityWorkspaceDataManager + implements UmbWorkspaceDataManager +{ + protected _varies?: boolean; + //#variesByCulture?: boolean; + //#variesBySegment?: boolean; + + #updateLock = 0; + initiatePropertyValueChange() { + this.#updateLock++; + this._current.mute(); + // TODO: When ready enable this code will enable handling a finish automatically by this implementation 'using myState.initiatePropertyValueChange()' (Relies on TS support of Using) [NL] + /*return { + [Symbol.dispose]: this.finishPropertyValueChange, + };*/ + } + finishPropertyValueChange = () => { + this.#updateLock--; + this.#triggerPropertyValueChanges(); + }; + #triggerPropertyValueChanges() { + if (this.#updateLock === 0) { + this._current.unmute(); + } + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + setVariesByCulture(vary: boolean | undefined) { + //this.#variesByCulture = vary; + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + setVariesBySegment(vary: boolean | undefined) { + //this.#variesBySegment = vary; + } + setVaries(vary: boolean | undefined) { + this._varies = vary; + } + + async constructData(selectedVariants: Array): Promise { + // Lets correct the selected variants, so invariant is included, or the only one if invariant. + // TODO: VDIVD: Could a document be set to invariant but hold variant data inside it? + const invariantVariantId = UmbVariantId.CreateInvariant(); + let variantsToStore = [invariantVariantId]; + if (this._varies === false) { + // If we do not vary, we wil just pick the invariant variant id. + selectedVariants = [invariantVariantId]; + } else { + variantsToStore = [...selectedVariants, invariantVariantId]; + } + + const data = this._current.getValue(); + if (!data) throw new Error('Current data is missing'); + //if (!data.unique) throw new Error('Unique of current data is missing'); + + const persistedData = this.getPersisted(); + + return await new UmbMergeContentVariantDataController(this).process( + persistedData, + data, + selectedVariants, + variantsToStore, + ); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/manager/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/manager/index.ts new file mode 100644 index 0000000000..f590f322ce --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/manager/index.ts @@ -0,0 +1,2 @@ +export * from './content-data-manager.js'; +export * from './element-data-manager.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/property-dataset-context/content-property-dataset.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/property-dataset-context/content-property-dataset.context.ts index 13551548c1..3fff502923 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content/property-dataset-context/content-property-dataset.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/property-dataset-context/content-property-dataset.context.ts @@ -1,162 +1,57 @@ import type { UmbContentWorkspaceContext } from '../workspace/index.js'; import type { UmbContentDetailModel } from '../types.js'; -import type { UmbNameablePropertyDatasetContext, UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property'; -import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; +import { UmbElementPropertyDatasetContext } from './element-property-dataset.context.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; -import { type Observable, map } from '@umbraco-cms/backoffice/external/rxjs'; -import { UmbBooleanState, UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; -import type { UmbEntityVariantModel } from '@umbraco-cms/backoffice/variant'; -import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; -import type { UmbContentTypeModel, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type'; -import type { UmbWorkspaceUniqueType } from '@umbraco-cms/backoffice/workspace'; +import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; +import type { UmbEntityVariantModel, UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import type { UmbContentTypeModel } from '@umbraco-cms/backoffice/content-type'; export class UmbContentPropertyDatasetContext< - ContentModel extends UmbContentDetailModel = UmbContentDetailModel, - ContentTypeModel extends UmbContentTypeModel = UmbContentTypeModel, - VariantModelType extends UmbEntityVariantModel = UmbEntityVariantModel, - > - extends UmbContextBase - implements UmbPropertyDatasetContext, UmbNameablePropertyDatasetContext -{ - #workspace: UmbContentWorkspaceContext; - #variantId: UmbVariantId; - public getVariantId() { - return this.#variantId; - } - - #currentVariant = new UmbObjectState(undefined); + ContentModel extends UmbContentDetailModel = UmbContentDetailModel, + ContentTypeModel extends UmbContentTypeModel = UmbContentTypeModel, + VariantModelType extends UmbEntityVariantModel = UmbEntityVariantModel, +> extends UmbElementPropertyDatasetContext< + ContentModel, + ContentTypeModel, + UmbContentWorkspaceContext +> { + // + #currentVariant = new UmbObjectState(undefined); currentVariant = this.#currentVariant.asObservable(); name = this.#currentVariant.asObservablePart((x) => x?.name); culture = this.#currentVariant.asObservablePart((x) => x?.culture); segment = this.#currentVariant.asObservablePart((x) => x?.segment); - #readOnly = new UmbBooleanState(false); - public readOnly = this.#readOnly.asObservable(); - - getEntityType(): string { - return this.#workspace.getEntityType(); - } - getUnique(): UmbWorkspaceUniqueType | undefined { - return this.#workspace.getUnique(); - } getName(): string | undefined { - return this.#workspace.getName(this.#variantId); + return this._dataOwner.getName(this.getVariantId()); } setName(name: string) { - this.#workspace.setName(name, this.#variantId); + this._dataOwner.setName(name, this.getVariantId()); } + /** + * @deprecated Its not clear why we have this. We should either document the need better or get rid of it. + * @returns {UmbEntityVariantModel | undefined} - gives information about the current variant. + */ getVariantInfo() { - return this.#workspace.getVariant(this.#variantId); - } - - getReadOnly() { - return this.#readOnly.getValue(); + return this._dataOwner.getVariant(this.getVariantId()); } constructor( host: UmbControllerHost, - workspace: UmbContentWorkspaceContext, + dataOwner: UmbContentWorkspaceContext, variantId?: UmbVariantId, ) { // The controller alias, is a very generic name cause we want only one of these for this controller host. - super(host, UMB_PROPERTY_DATASET_CONTEXT); - this.#workspace = workspace; - this.#variantId = variantId ?? UmbVariantId.CreateInvariant(); + super(host, dataOwner, variantId); this.observe( - this.#workspace.variantById(this.#variantId), + this._dataOwner.variantById(this.getVariantId()), async (variantInfo) => { if (!variantInfo) return; this.#currentVariant.setValue(variantInfo); }, - '_observeActiveVariant', + null, ); - - this.observe( - this.#workspace.readOnlyState.states, - (states) => { - const isReadOnly = states.some((state) => state.variantId.equal(this.#variantId)); - this.#readOnly.setValue(isReadOnly); - }, - 'umbObserveReadOnlyStates', - ); - } - - #createPropertyVariantId(property: UmbPropertyTypeModel) { - return UmbVariantId.Create({ - culture: property.variesByCulture ? this.#variantId.culture : null, - segment: property.variesBySegment ? this.#variantId.segment : null, - }); - } - - /** - * @function propertyVariantId - * @param {string} propertyAlias - * @returns {Promise | undefined>} - * @description Get an Observable for the variant id of this property. - */ - async propertyVariantId(propertyAlias: string) { - return (await this.#workspace.structure.propertyStructureByAlias(propertyAlias)).pipe( - map((property) => (property ? this.#createPropertyVariantId(property) : undefined)), - ); - } - - /** - * @function propertyValueByAlias - * @param {string} propertyAlias - * @returns {Promise | undefined>} - * @description Get an Observable for the value of this property. - */ - async propertyValueByAlias( - propertyAlias: string, - ): Promise | undefined> { - await this.#workspace.isLoaded(); - const structure = await this.#workspace.structure.getPropertyStructureByAlias(propertyAlias); - if (structure) { - return this.#workspace.propertyValueByAlias(propertyAlias, this.#createPropertyVariantId(structure)); - } - return; - } - - // TODO: Refactor: Not used currently, but should investigate if we can implement this, to spare some energy. - async propertyValueByAliasAndVariantId( - propertyAlias: string, - propertyVariantId: UmbVariantId, - ): Promise | undefined> { - return this.#workspace.propertyValueByAlias(propertyAlias, propertyVariantId); - } - - /** - * @function setPropertyValueByVariant - * @param {string} propertyAlias - * @param {unknown} value - value can be a promise resolving into the actual value or the raw value it self. - * @param {UmbVariantId} propertyVariantId - The variant id for the value to be set for. - * @returns {Promise} - * @description Get the value of this property. - */ - setPropertyValueByVariant(propertyAlias: string, value: unknown, propertyVariantId: UmbVariantId): Promise { - return this.#workspace.setPropertyValue(propertyAlias, value, propertyVariantId); - } - - /** - * @function setPropertyValue - * @param {string} propertyAlias - * @param {PromiseLike} value - value can be a promise resolving into the actual value or the raw value it self. - * @returns {Promise} - * @description Set the value of this property. - */ - async setPropertyValue(propertyAlias: string, value: PromiseLike) { - this.#workspace.initiatePropertyValueChange(); - // This is not reacting to if the property variant settings changes while running. - const property = await this.#workspace.structure.getPropertyStructureByAlias(propertyAlias); - if (property) { - const variantId = this.#createPropertyVariantId(property); - - // This is not reacting to if the property variant settings changes while running. - this.#workspace.setPropertyValue(propertyAlias, await value, variantId); - } - this.#workspace.finishPropertyValueChange(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/property-dataset-context/element-property-data-owner.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/property-dataset-context/element-property-data-owner.interface.ts new file mode 100644 index 0000000000..142f7172de --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/property-dataset-context/element-property-data-owner.interface.ts @@ -0,0 +1,36 @@ +import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; +import type { UmbPropertyValueData } from '@umbraco-cms/backoffice/property'; +import type { UmbContentTypeModel, UmbContentTypeStructureManager } from '@umbraco-cms/backoffice/content-type'; +import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; +import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; +import type { UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; + +/** + * The data supplier for a Element Property Dataset + */ +export interface UmbElementPropertyDataOwner< + ContentModel extends { values: Array }, + ContentTypeModel extends UmbContentTypeModel = UmbContentTypeModel, +> extends UmbApi { + unique: Observable; + getUnique(): UmbEntityUnique | undefined; + getEntityType(): string; + readonly structure: UmbContentTypeStructureManager; + readonly values: Observable; + getValues(): ContentModel['values'] | undefined; + + isLoaded(): Promise | undefined; + readonly readOnlyState: UmbReadOnlyVariantStateManager; + + // Same as from UmbVariantDatasetWorkspaceContext, could be refactored later [NL] + propertyValueByAlias( + alias: string, + variantId?: UmbVariantId, + ): Promise | undefined>; + getPropertyValue(alias: string, variantId?: UmbVariantId): ReturnValue | undefined; + setPropertyValue(alias: string, value: unknown, variantId?: UmbVariantId): Promise; + + initiatePropertyValueChange(): void; + finishPropertyValueChange(): void; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts new file mode 100644 index 0000000000..0490888eb7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts @@ -0,0 +1,221 @@ +import type { UmbElementDetailModel } from '../types.js'; +import type { UmbElementPropertyDataOwner } from './element-property-data-owner.interface.js'; +import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property'; +import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; +import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; +import { + UmbBasicState, + UmbBooleanState, + classEqualMemoization, + createObservablePart, + mergeObservables, +} from '@umbraco-cms/backoffice/observable-api'; +import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; +import type { UmbContentTypeModel, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type'; +import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; + +type UmbPropertyVariantIdMapType = Array<{ alias: string; variantId: UmbVariantId }>; + +export abstract class UmbElementPropertyDatasetContext< + ContentModel extends UmbElementDetailModel = UmbElementDetailModel, + ContentTypeModel extends UmbContentTypeModel = UmbContentTypeModel, + DataOwnerType extends UmbElementPropertyDataOwner = UmbElementPropertyDataOwner< + ContentModel, + ContentTypeModel + >, + > + extends UmbContextBase + implements UmbPropertyDatasetContext +{ + protected readonly _dataOwner: DataOwnerType; + #variantId: UmbVariantId; + public getVariantId() { + return this.#variantId; + } + + abstract name: Observable; + abstract culture: Observable; + abstract segment: Observable; + + #propertyVariantIdPromise?: Promise; + #propertyVariantIdPromiseResolver?: () => void; + #propertyVariantIdMap = new UmbBasicState([]); + private readonly _propertyVariantIdMap = this.#propertyVariantIdMap.asObservable(); + + #readOnly = new UmbBooleanState(false); + public readOnly = this.#readOnly.asObservable(); + + getEntityType(): string { + return this._dataOwner.getEntityType(); + } + getUnique(): UmbEntityUnique | undefined { + return this._dataOwner.getUnique(); + } + abstract getName(): string | undefined; + + getReadOnly() { + return this.#readOnly.getValue(); + } + + constructor(host: UmbControllerHost, dataOwner: DataOwnerType, variantId?: UmbVariantId) { + // The controller alias, is a very generic name cause we want only one of these for this controller host. + super(host, UMB_PROPERTY_DATASET_CONTEXT); + this._dataOwner = dataOwner; + this.#variantId = variantId ?? UmbVariantId.CreateInvariant(); + + this.#propertyVariantIdPromise = new Promise((resolve) => { + this.#propertyVariantIdPromiseResolver = resolve as any; + }); + + this.observe( + this._dataOwner.readOnlyState.states, + (states) => { + const isReadOnly = states.some((state) => state.variantId.equal(this.#variantId)); + this.#readOnly.setValue(isReadOnly); + }, + null, + ); + + // TODO: Refactor this into a separate manager/controller of some sort? [NL] + this.observe( + this._dataOwner.structure.contentTypeProperties, + (props: UmbPropertyTypeModel[]) => { + const map = props.map((prop) => ({ alias: prop.alias, variantId: this.#createPropertyVariantId(prop) })); + this.#propertyVariantIdMap.setValue(map); + // Resolve promise, to let the once waiting on this know. + if (this.#propertyVariantIdPromiseResolver) { + this.#propertyVariantIdPromiseResolver(); + this.#propertyVariantIdPromiseResolver = undefined; + this.#propertyVariantIdPromise = undefined; + } + }, + null, + ); + } + + #createPropertyVariantId(property: UmbPropertyTypeModel) { + return UmbVariantId.Create({ + culture: property.variesByCulture ? this.#variantId.culture : null, + segment: property.variesBySegment ? this.#variantId.segment : null, + }); + } + + #propertiesObservable?: Observable; + // Should it be possible to get the properties as a list of property aliases? + get properties(): Observable { + if (!this.#propertiesObservable) { + this.#propertiesObservable = mergeObservables( + [this._propertyVariantIdMap, this._dataOwner.values], + this.#mergeVariantIdsAndValues, + ); + } + + return this.#propertiesObservable; + } + + #mergeVariantIdsAndValues([props, values]: [UmbPropertyVariantIdMapType, ContentModel['values'] | undefined]) { + const r: ContentModel['values'] = []; + if (values) { + for (const prop of props) { + const f = values.find((v) => prop.alias === v.alias && prop.variantId.compare(v)); + if (f) { + r.push(f); + } + } + } + return r as ContentModel['values']; + } + + async getProperties(): Promise { + await this.#propertyVariantIdPromise; + return this.#mergeVariantIdsAndValues([ + this.#propertyVariantIdMap.getValue(), + this._dataOwner.getValues(), + ]) as ContentModel['values']; + } + + /** + * @function propertyVariantId + * @param {string} propertyAlias - The property alias to observe the variantId of. + * @returns {Promise | undefined>} - The observable for this properties variantId. + * @description Get an Observable for the variant id of this property. + */ + async propertyVariantId(propertyAlias: string) { + /* + return (await this.#workspace.structure.propertyStructureByAlias(propertyAlias)).pipe( + map((property) => (property ? this.#createPropertyVariantId(property) : undefined)), + ); + */ + return createObservablePart( + this._propertyVariantIdMap, + (x) => x.find((v) => v.alias === propertyAlias)?.variantId, + classEqualMemoization, + ); + } + + /** + * @function propertyValueByAlias + * @param {string} propertyAlias The alias of the property + * @returns {Promise | undefined>} - An observable of the property value + * @description Get an Observable for the value of this property. + */ + async propertyValueByAlias( + propertyAlias: string, + ): Promise | undefined> { + await this._dataOwner.isLoaded(); + await this.#propertyVariantIdPromise; + return mergeObservables( + [await this.propertyVariantId(propertyAlias), this._dataOwner.values], + ([variantId, values]) => { + return variantId + ? (values?.find((x) => x?.alias === propertyAlias && variantId.compare(x))?.value as ReturnType) + : undefined; + }, + ); + } + + // TODO: Refactor: Not used currently, but should investigate if we can implement this, to spare some energy. + async propertyValueByAliasAndVariantId( + propertyAlias: string, + propertyVariantId: UmbVariantId, + ): Promise | undefined> { + return this._dataOwner.propertyValueByAlias(propertyAlias, propertyVariantId); + } + + /** + * @function setPropertyValueByVariant + * @param {string} propertyAlias - The alias of the property + * @param {unknown} value - value can be a promise resolving into the actual value or the raw value it self. + * @param {UmbVariantId} propertyVariantId - The variant id for the value to be set for. + * @returns {Promise} - A promise that resolves once the value has been set. + * @description Get the value of this property. + */ + setPropertyValueByVariant(propertyAlias: string, value: unknown, propertyVariantId: UmbVariantId): Promise { + return this._dataOwner.setPropertyValue(propertyAlias, value, propertyVariantId); + } + + /** + * @function setPropertyValue + * @param {string} propertyAlias - The alias for the value to be set + * @param {PromiseLike} value - value can be a promise resolving into the actual value or the raw value it self. + * @returns {Promise} + * @description Set the value of this property. + */ + async setPropertyValue(propertyAlias: string, value: PromiseLike) { + this._dataOwner.initiatePropertyValueChange(); + await this.#propertyVariantIdPromise; + const propVariantId = this.#propertyVariantIdMap.getValue().find((x) => x.alias === propertyAlias)?.variantId; + if (propVariantId) { + await this._dataOwner.setPropertyValue(propertyAlias, await value, propVariantId); + } + this._dataOwner.finishPropertyValueChange(); + } + + override destroy() { + super.destroy(); + this.#propertyVariantIdMap.destroy(); + (this.#propertyVariantIdMap as unknown) = undefined; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/property-dataset-context/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/property-dataset-context/index.ts new file mode 100644 index 0000000000..69c6bea4c8 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/property-dataset-context/index.ts @@ -0,0 +1,3 @@ +export * from './content-property-dataset.context.js'; +export * from './element-property-data-owner.interface.js'; +export * from './element-property-dataset.context.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/types.ts index e049e2250a..a1e7c8cd01 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/types.ts @@ -1,11 +1,17 @@ import type { UmbPropertyValueData } from '@umbraco-cms/backoffice/property'; import type { UmbEntityVariantModel } from '@umbraco-cms/backoffice/variant'; -export interface UmbContentValueModel extends UmbPropertyValueData { +export interface UmbElementDetailModel { + values: Array; +} + +export interface UmbElementValueModel extends UmbPropertyValueData { editorAlias: string; culture: string | null; segment: string | null; } +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface UmbContentValueModel extends UmbElementValueModel {} export interface UmbPotentialContentValueModel extends UmbPropertyValueData { editorAlias?: string; @@ -13,9 +19,12 @@ export interface UmbPotentialContentValueModel extends UmbP segment?: string | null; } -export interface UmbContentDetailModel { +export interface UmbContentDetailModel extends UmbElementDetailModel { unique: string; entityType: string; - values: Array; variants: Array; } + +export interface UmbContentLikeDetailModel + extends UmbElementDetailModel, + Partial> {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-workspace-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-workspace-context.interface.ts index c9fd0665cb..ecaeca41a7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-workspace-context.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/content/workspace/content-workspace-context.interface.ts @@ -1,7 +1,6 @@ -import type { UmbContentDetailModel } from '@umbraco-cms/backoffice/content'; +import type { UmbContentDetailModel, UmbElementPropertyDataOwner } from '@umbraco-cms/backoffice/content'; import type { UmbContentTypeModel } from '@umbraco-cms/backoffice/content-type'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; -import type { UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; import type { UmbVariantId, UmbEntityVariantModel } from '@umbraco-cms/backoffice/variant'; import type { UmbPropertyStructureWorkspaceContext, @@ -13,11 +12,13 @@ export interface UmbContentWorkspaceContext< ContentModel extends UmbContentDetailModel = UmbContentDetailModel, ContentTypeModel extends UmbContentTypeModel = UmbContentTypeModel, VariantModelType extends UmbEntityVariantModel = UmbEntityVariantModel, -> extends UmbRoutableWorkspaceContext, +> extends UmbElementPropertyDataOwner, + UmbRoutableWorkspaceContext, UmbVariantDatasetWorkspaceContext, UmbPropertyStructureWorkspaceContext { readonly IS_CONTENT_WORKSPACE_CONTEXT: true; - readonly readOnlyState: UmbReadOnlyVariantStateManager; + //readonly values: Observable; + //getValues(): ContentModel['values'] | undefined; // Data: getData(): ContentModel | undefined; @@ -25,6 +26,6 @@ export interface UmbContentWorkspaceContext< isLoaded(): Promise | undefined; variantById(variantId: UmbVariantId): Observable; - initiatePropertyValueChange(): void; - finishPropertyValueChange(): void; + //initiatePropertyValueChange(): void; + //finishPropertyValueChange(): void; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/create.action.kind.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/create.action.kind.ts new file mode 100644 index 0000000000..a220d955aa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/create.action.kind.ts @@ -0,0 +1,22 @@ +import { UMB_ENTITY_ACTION_DEFAULT_KIND_MANIFEST } from '../../default/default.action.kind.js'; +import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifest: UmbExtensionManifestKind = { + type: 'kind', + alias: 'Umb.Kind.EntityAction.Create', + matchKind: 'create', + matchType: 'entityAction', + manifest: { + ...UMB_ENTITY_ACTION_DEFAULT_KIND_MANIFEST.manifest, + type: 'entityAction', + kind: 'create', + api: () => import('./create.action.js'), + weight: 1200, + forEntityTypes: [], + meta: { + icon: 'icon-add', + label: '#actions_create', + additionalOptions: true, + }, + }, +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/create.action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/create.action.ts new file mode 100644 index 0000000000..e341354768 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/create.action.ts @@ -0,0 +1,59 @@ +import { UmbEntityActionBase } from '../../entity-action-base.js'; +import type { UmbEntityActionArgs } from '../../types.js'; +import type { MetaEntityActionCreateKind } from './types.js'; +import { UMB_ENTITY_CREATE_OPTION_ACTION_LIST_MODAL } from './modal/index.js'; +import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { createExtensionApi, UmbExtensionsManifestInitializer } from '@umbraco-cms/backoffice/extension-api'; +import type { ManifestEntityCreateOptionAction } from '@umbraco-cms/backoffice/entity-create-option-action'; + +export class UmbCreateEntityAction extends UmbEntityActionBase { + #hasSingleOption = true; + #singleActionOptionManifest?: ManifestEntityCreateOptionAction; + + constructor(host: UmbControllerHost, args: UmbEntityActionArgs) { + super(host, args); + + new UmbExtensionsManifestInitializer( + this, + umbExtensionsRegistry, + 'entityCreateOptionAction', + (ext) => ext.forEntityTypes.includes(this.args.entityType), + async (actionOptions) => { + this.#hasSingleOption = actionOptions.length === 1; + this.#singleActionOptionManifest = this.#hasSingleOption + ? (actionOptions[0].manifest as unknown as ManifestEntityCreateOptionAction) + : undefined; + }, + 'umbEntityActionsObserver', + ); + } + + override async execute() { + if (this.#hasSingleOption) { + if (!this.#singleActionOptionManifest) throw new Error('No first action manifest found'); + + const api = await createExtensionApi(this, this.#singleActionOptionManifest, [ + { unique: this.args.unique, entityType: this.args.entityType, meta: this.#singleActionOptionManifest.meta }, + ]); + + if (!api) throw new Error(`Could not create api for ${this.#singleActionOptionManifest.alias}`); + + await api.execute(); + return; + } + + const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); + const modalContext = modalManager.open(this, UMB_ENTITY_CREATE_OPTION_ACTION_LIST_MODAL, { + data: { + unique: this.args.unique, + entityType: this.args.entityType, + }, + }); + + await modalContext.onSubmit(); + } +} + +export { UmbCreateEntityAction as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/index.ts new file mode 100644 index 0000000000..d44c5eca74 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/index.ts @@ -0,0 +1 @@ +export { UmbCreateEntityAction } from './create.action.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/manifests.ts new file mode 100644 index 0000000000..d1e496f559 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/manifests.ts @@ -0,0 +1,4 @@ +import { manifest as createKindManifest } from './create.action.kind.js'; +import { manifests as modalManifests } from './modal/manifests.js'; + +export const manifests = [createKindManifest, ...modalManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/modal/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/modal/constants.ts new file mode 100644 index 0000000000..ab81577a04 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/modal/constants.ts @@ -0,0 +1 @@ +export const UMB_ENTITY_CREATE_OPTION_ACTION_LIST_MODAL_ALIAS = 'Umb.Modal.Entity.CreateOptionActionList'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/modal/entity-create-option-action-list-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/modal/entity-create-option-action-list-modal.element.ts new file mode 100644 index 0000000000..5150c3946d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/modal/entity-create-option-action-list-modal.element.ts @@ -0,0 +1,134 @@ +import type { + UmbEntityCreateOptionActionListModalData, + UmbEntityCreateOptionActionListModalValue, +} from './entity-create-option-action-list-modal.token.js'; +import type { ManifestEntityCreateOptionAction } from '@umbraco-cms/backoffice/entity-create-option-action'; +import type { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api'; +import { UmbExtensionsApiInitializer } from '@umbraco-cms/backoffice/extension-api'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; +import { + html, + customElement, + state, + repeat, + ifDefined, + type PropertyValues, +} from '@umbraco-cms/backoffice/external/lit'; +import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; + +type ManifestType = ManifestEntityCreateOptionAction; + +const elementName = 'umb-entity-create-option-action-list-modal'; +@customElement(elementName) +export class UmbEntityCreateOptionActionListModalElement extends UmbModalBaseElement< + UmbEntityCreateOptionActionListModalData, + UmbEntityCreateOptionActionListModalValue +> { + @state() + private _apiControllers: Array> = []; + + @state() + _hrefList: Array = []; + + protected override updated(_changedProperties: PropertyValues): void { + super.updated(_changedProperties); + + if (_changedProperties.has('data')) { + this.#initApi(); + } + } + + #initApi() { + const data = this.data; + if (!data) throw new Error('No data found'); + + if (!data.entityType) throw new Error('No entityType found'); + if (data.unique === undefined) throw new Error('No unique found'); + + new UmbExtensionsApiInitializer( + this, + umbExtensionsRegistry, + 'entityCreateOptionAction', + (manifest: ManifestType) => { + return [{ entityType: data.entityType, unique: data.unique, meta: manifest.meta }]; + }, + (manifest: ManifestType) => manifest.forEntityTypes.includes(data.entityType), + async (controllers) => { + this._apiControllers = controllers as unknown as Array>; + const hrefPromises = this._apiControllers.map((controller) => controller.api?.getHref()); + this._hrefList = await Promise.all(hrefPromises); + }, + ); + } + + async #onClick(event: Event, controller: UmbExtensionApiInitializer, href?: string) { + event.stopPropagation(); + + // skip if href is defined + if (href) { + return; + } + + if (!controller.api) throw new Error('No API found'); + await controller.api.execute(); + } + + #getTarget(href?: string) { + if (href && href.startsWith('http')) { + return '_blank'; + } + + return '_self'; + } + + override render() { + return html` + + + ${this._apiControllers.length === 0 + ? html`
No create options available.
` + : html` + ${repeat( + this._apiControllers, + (controller) => controller.manifest?.alias, + (controller, index) => this.#renderRefItem(controller, index), + )} + `} +
+ +
+ `; + } + + #renderRefItem(controller: UmbExtensionApiInitializer, index: number) { + const manifest = controller.manifest; + if (!manifest) throw new Error('No manifest found'); + + const label = manifest.meta.label ? this.localize.string(manifest.meta.label) : manifest.name; + const href = this._hrefList[index]; + + return html` + this.#onClick(event, controller, href)} + href=${ifDefined(href)} + target=${this.#getTarget(href)} + ?selectable=${!href} + ?readonly=${!href}> + + + `; + } +} + +export { UmbEntityCreateOptionActionListModalElement as element }; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: UmbEntityCreateOptionActionListModalElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/modal/entity-create-option-action-list-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/modal/entity-create-option-action-list-modal.token.ts new file mode 100644 index 0000000000..dd1aba66b4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/modal/entity-create-option-action-list-modal.token.ts @@ -0,0 +1,20 @@ +import { UMB_ENTITY_CREATE_OPTION_ACTION_LIST_MODAL_ALIAS } from './constants.js'; +import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; + +export interface UmbEntityCreateOptionActionListModalData { + unique: UmbEntityUnique; + entityType: string; +} + +export type UmbEntityCreateOptionActionListModalValue = never; + +export const UMB_ENTITY_CREATE_OPTION_ACTION_LIST_MODAL = new UmbModalToken< + UmbEntityCreateOptionActionListModalData, + UmbEntityCreateOptionActionListModalValue +>(UMB_ENTITY_CREATE_OPTION_ACTION_LIST_MODAL_ALIAS, { + modal: { + type: 'sidebar', + size: 'small', + }, +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/modal/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/modal/index.ts new file mode 100644 index 0000000000..876d392b23 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/modal/index.ts @@ -0,0 +1,2 @@ +export * from './constants.js'; +export * from './entity-create-option-action-list-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/modal/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/modal/manifests.ts new file mode 100644 index 0000000000..2b6ee6ed47 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/modal/manifests.ts @@ -0,0 +1,10 @@ +import { UMB_ENTITY_CREATE_OPTION_ACTION_LIST_MODAL_ALIAS } from './constants.js'; + +export const manifests: Array = [ + { + type: 'modal', + alias: UMB_ENTITY_CREATE_OPTION_ACTION_LIST_MODAL_ALIAS, + name: 'Entity Create Option Action List Modal', + element: () => import('./entity-create-option-action-list-modal.element.js'), + }, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/types.ts new file mode 100644 index 0000000000..7d102e750a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/create/types.ts @@ -0,0 +1,16 @@ +import type { MetaEntityActionDefaultKind } from '../../default/index.js'; +import type { ManifestEntityAction } from '../../entity-action.extension.js'; + +export interface ManifestEntityActionCreateKind extends ManifestEntityAction { + type: 'entityAction'; + kind: 'create'; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface MetaEntityActionCreateKind extends MetaEntityActionDefaultKind {} + +declare global { + interface UmbExtensionManifestMap { + umbEntityActionCreateKind: ManifestEntityActionCreateKind; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/index.ts index 30877954f8..5a7c2ae86b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/common/index.ts @@ -1,2 +1,3 @@ -export * from './duplicate/index.js'; +export * from './create/index.js'; export * from './delete/index.js'; +export * from './duplicate/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/index.ts index 0fd5ba6de0..60881da438 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/index.ts @@ -5,8 +5,8 @@ export * from './entity-action-list.element.js'; export * from './entity-action.event.js'; export * from './entity-action.extension.js'; export * from './entity-action.interface.js'; -export * from './types.js'; export type * from './entity-action-element.interface.js'; +export type * from './types.js'; export { UmbRequestReloadStructureForEntityEvent } from './request-reload-structure-for-entity.event.js'; export { UmbRequestReloadChildrenOfEntityEvent } from './request-reload-children-of-entity.event.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/manifests.ts index aeaaceafc0..7d6813e49a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/manifests.ts @@ -1,9 +1,12 @@ +import { manifests as createEntityActionManifests } from './common/create/manifests.js'; import { manifests as defaultEntityActionManifests } from './default/manifests.js'; import { manifests as deleteEntityActionManifests } from './common/delete/manifests.js'; import { manifests as duplicateEntityActionManifests } from './common/duplicate/manifests.js'; + import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; export const manifests: Array = [ + ...createEntityActionManifests, ...defaultEntityActionManifests, ...deleteEntityActionManifests, ...duplicateEntityActionManifests, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-bulk-action/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-bulk-action/index.ts index bf14ffa917..601493c5b2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-bulk-action/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-bulk-action/index.ts @@ -1,8 +1,8 @@ -export * from './types.js'; export * from './common/index.js'; export * from './entity-bulk-action-base.js'; export * from './entity-bulk-action.element.js'; export * from './entity-bulk-action.interface.js'; export type * from './entity-bulk-action-element.interface.js'; +export type * from './types.js'; export { UMB_ENTITY_BULK_ACTION_DEFAULT_KIND_MANIFEST } from './default/default.action.kind.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-create-option-action/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-create-option-action/constants.ts new file mode 100644 index 0000000000..4c25e59099 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-create-option-action/constants.ts @@ -0,0 +1 @@ +export const UMB_EXTENSION_TYPE_ENTITY_CREATE_OPTION_ACTION = 'entityCreateOptionAction'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-create-option-action/entity-create-option-action-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-create-option-action/entity-create-option-action-base.ts new file mode 100644 index 0000000000..236d7e6394 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-create-option-action/entity-create-option-action-base.ts @@ -0,0 +1,30 @@ +import type { UmbEntityCreateOptionActionArgs } from './types.js'; +import type { UmbEntityCreateOptionAction } from './entity-create-option-action.interface.js'; +import type { MetaEntityCreateOptionAction } from './entity-create-option-action.extension.js'; +import { UmbActionBase } from '@umbraco-cms/backoffice/action'; + +export abstract class UmbEntityCreateOptionActionBase< + ArgsMetaType extends MetaEntityCreateOptionAction = MetaEntityCreateOptionAction, + > + extends UmbActionBase> + implements UmbEntityCreateOptionAction +{ + /** + * By specifying the href, the action will act as a link. + * The `execute` method will not be called. + * @abstract + * @returns {string | undefined} - A promise which resolves into a HREF string or undefined. + */ + public getHref(): Promise { + return Promise.resolve(undefined); + } + + /** + * By specifying the `execute` method, the action will act as a button. + * @abstract + * @returns {Promise} + */ + public execute(): Promise { + return Promise.resolve(); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-create-option-action/entity-create-option-action.extension.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-create-option-action/entity-create-option-action.extension.ts new file mode 100644 index 0000000000..4f62ecd3de --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-create-option-action/entity-create-option-action.extension.ts @@ -0,0 +1,54 @@ +import type { UmbEntityCreateOptionAction } from './entity-create-option-action.interface.js'; +import type { UMB_EXTENSION_TYPE_ENTITY_CREATE_OPTION_ACTION } from './constants.js'; +import type { ManifestApi, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api'; + +export interface ManifestEntityCreateOptionAction< + MetaType extends MetaEntityCreateOptionAction = MetaEntityCreateOptionAction, +> extends ManifestApi>, + ManifestWithDynamicConditions { + type: typeof UMB_EXTENSION_TYPE_ENTITY_CREATE_OPTION_ACTION; + forEntityTypes: Array; + meta: MetaType; +} + +export interface MetaEntityCreateOptionAction { + /** + * An icon to represent the action to be performed + * @examples [ + * "icon-box", + * "icon-grid" + * ] + */ + icon: string; + + /** + * The friendly name of the action to perform + * @examples [ + * "Create with Template", + * "Create from Blueprint" + * ] + */ + label: string; + + /** + * A description of the action to be performed + * @examples [ + * "Create a document type with a template", + * "Create a document from a blueprint" + * ] + */ + description?: string; + + /** + * The action requires additional input from the user. + * A dialog will prompt the user for more information or to make a choice. + * @type {boolean} + */ + additionalOptions?: boolean; +} + +declare global { + interface UmbExtensionManifestMap { + umbEntityCreateOptionAction: ManifestEntityCreateOptionAction; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-create-option-action/entity-create-option-action.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-create-option-action/entity-create-option-action.interface.ts new file mode 100644 index 0000000000..141e9dd084 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-create-option-action/entity-create-option-action.interface.ts @@ -0,0 +1,19 @@ +import type { MetaEntityCreateOptionAction } from './entity-create-option-action.extension.js'; +import type { UmbEntityCreateOptionActionArgs } from './types.js'; +import type { UmbAction } from '@umbraco-cms/backoffice/action'; + +export interface UmbEntityCreateOptionAction< + ArgsMetaType extends MetaEntityCreateOptionAction = MetaEntityCreateOptionAction, +> extends UmbAction> { + /** + * The href location, the action will act as a link. + * @returns {Promise} + */ + getHref(): Promise; + + /** + * The `execute` method, the action will act as a button. + * @returns {Promise} + */ + execute(): Promise; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-create-option-action/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-create-option-action/index.ts new file mode 100644 index 0000000000..566d9306e5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-create-option-action/index.ts @@ -0,0 +1,5 @@ +export * from './constants.js'; +export * from './entity-create-option-action-base.js'; +export * from './entity-create-option-action.extension.js'; +export * from './entity-create-option-action.interface.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-create-option-action/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-create-option-action/types.ts new file mode 100644 index 0000000000..ca6db2fc24 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-create-option-action/types.ts @@ -0,0 +1,5 @@ +import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; + +export interface UmbEntityCreateOptionActionArgs extends UmbEntityModel { + meta: MetaArgsType; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity/index.ts index d3f60a90c0..737d7bf482 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity/index.ts @@ -1,3 +1,3 @@ export { UMB_ENTITY_CONTEXT } from './entity.context-token.js'; export { UmbEntityContext } from './entity.context.js'; -export * from './types.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/conditions/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/conditions/types.ts index 9005394877..ee46b35085 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/conditions/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/conditions/types.ts @@ -1,13 +1,7 @@ -import type { CollectionAliasConditionConfig } from '../../collection/collection-alias.manifest.js'; -import type { CollectionBulkActionPermissionConditionConfig } from '../../collection/collection-bulk-action-permission.manifest.js'; import type { SwitchConditionConfig } from './switch.condition.js'; import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; -export type UmbCoreConditionConfigs = - | CollectionAliasConditionConfig - | CollectionBulkActionPermissionConditionConfig - | SwitchConditionConfig - | UmbConditionConfigBase; +export type UmbCoreConditionConfigs = SwitchConditionConfig | UmbConditionConfigBase; /** * @deprecated instead use global UmbExtensionConditionConfig diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts index 7c562c37af..2b3c1e5178 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/models/index.ts @@ -1,6 +1,29 @@ import type { ManifestBase, ManifestBundle, ManifestCondition } from '@umbraco-cms/backoffice/extension-api'; -export type ManifestTypes = ManifestBundle | ManifestCondition | ManifestBase; +/** + * @deprecated Follow these steps to get Extension Manifest Type for v.15+ projects: + * + * Setup your `tsconfig.json` to include the extension-types as global types. Like this: + * ``` + { + "compilerOptions": { + ... + "types": [ + "@umbraco-cms/backoffice/extension-types" + ] + } + } + * ``` + * + * Once done, you can use the global type `UmbExtensionManifest`. + * + * If defining your own extension types, then follow the link below for more information. + * + * [Read more on the change announcement]{https://github.com/umbraco/Announcements/issues/22} + */ +export type ManifestTypes = never; + +type UmbCoreManifestTypes = ManifestBundle | ManifestCondition | ManifestBase; type UnionOfProperties = T extends object ? T[keyof T] : never; @@ -25,7 +48,7 @@ declare global { ``` */ interface UmbExtensionManifestMap { - UMB_CORE: ManifestTypes; + UMB_CORE: UmbCoreManifestTypes; } /** diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/registry.ts b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/registry.ts index 0985b3226e..22849345b6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/registry.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/registry.ts @@ -2,9 +2,12 @@ import type { ManifestKind } from '@umbraco-cms/backoffice/extension-api'; import { UmbExtensionRegistry } from '@umbraco-cms/backoffice/extension-api'; export type UmbExtensionManifestKind = ManifestKind; -export type UmbBackofficeExtensionRegistry = UmbExtensionRegistry; +export type UmbBackofficeExtensionRegistry = UmbExtensionRegistry; -export const umbExtensionsRegistry = new UmbExtensionRegistry() as UmbBackofficeExtensionRegistry; +export const umbExtensionsRegistry = new UmbExtensionRegistry< + UmbExtensionManifest, + UmbExtensionConditionConfig +>() as UmbBackofficeExtensionRegistry; /** * @deprecated Use `UmbExtensionManifestKind` instead. diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json index cc6923a5d9..d358487927 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json @@ -366,6 +366,10 @@ "name": "icon-circuits", "file": "circuit-board.svg" }, + { + "name": "icon-clear-formatting", + "file": "remove-formatting.svg" + }, { "name": "icon-client", "file": "user.svg", diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts index 6edd8ec784..8855310df5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons.ts @@ -355,6 +355,10 @@ name: "icon-circuits", path: () => import("./icons/icon-circuits.js"), },{ +name: "icon-clear-formatting", + +path: () => import("./icons/icon-clear-formatting.js"), +},{ name: "icon-client", legacy: true, path: () => import("./icons/icon-client.js"), diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-clear-formatting.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-clear-formatting.ts new file mode 100644 index 0000000000..09fbab564a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icons/icon-clear-formatting.ts @@ -0,0 +1,18 @@ +export default ` + + + + + + + +`; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/index.ts index 1da66f89d4..d2d7785f3b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/index.ts @@ -2,5 +2,5 @@ export * from './icon-picker-modal/index.js'; export * from './icon-registry.context-token.js'; export * from './icon-registry.context.js'; export * from './icon.registry.js'; -export * from './types.js'; +export type * from './types.js'; export type * from './extensions/icons.extension.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/localization/stories/localize.element.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/localization/stories/localize.element.stories.ts index 7b3f352e9d..cef1a3f7ee 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/localization/stories/localize.element.stories.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/localization/stories/localize.element.stories.ts @@ -9,13 +9,6 @@ const meta: Meta = { args: { key: 'general_areyousure', }, - argTypes: { - args: { - control: { - type: 'text', - }, - }, - }, decorators: [ (story) => { return html`
diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/index.ts index 255e8a9ae8..1509c986d2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/index.ts @@ -1,9 +1,9 @@ export * from './components/index.js'; export * from './menu-tree-structure-workspace-context-base.js'; export * from './menu-variant-tree-structure-workspace-context-base.js'; -export * from './types.js'; export type * from './menu-item-element.interface.js'; export type * from './menu-item.extension.js'; export type * from './menu.extension.js'; +export type * from './types.js'; export type { UmbMenuStructureWorkspaceContext } from './menu-structure-workspace-context.interface.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts index 82ba7df803..8d89588a1d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/menu/menu-variant-tree-structure-workspace-context-base.ts @@ -45,7 +45,7 @@ export abstract class UmbMenuVariantTreeStructureWorkspaceContextBase extends Um let structureItems: Array = []; const unique = (await this.observe(uniqueObservable, () => {})?.asPromise()) as string; - if (!unique) throw new Error('Unique is not available'); + if (unique === undefined) throw new Error('Unique is not available'); const entityType = (await this.observe(entityTypeObservable, () => {})?.asPromise()) as string; if (!entityType) throw new Error('Entity type is not available'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/index.ts index 55ca1f1885..5403700cff 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/index.ts @@ -5,4 +5,4 @@ export * from './component/modal-base.element.js'; export * from './component/modal.element.js'; export * from './context/index.js'; export * from './token/index.js'; -export * from './types.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/types.ts index 3549e429c8..a1c61b09c0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/types.ts @@ -1,4 +1,4 @@ -export * from './extensions/index.js'; +export type * from './extensions/index.js'; export interface UmbPickerModalData { multiple?: boolean; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/picker/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/picker/index.ts index b19f3e47db..0074a61d85 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/picker/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/picker/index.ts @@ -1,4 +1,4 @@ export * from './search/index.js'; export * from './picker.context.js'; export * from './picker.context.token.js'; -export * from './types.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/extensions/property-editor-ui-element.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/extensions/property-editor-ui-element.interface.ts index 39f40cde5b..58163db2d9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/extensions/property-editor-ui-element.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-editor/extensions/property-editor-ui-element.interface.ts @@ -1,8 +1,10 @@ import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; export interface UmbPropertyEditorUiElement extends HTMLElement { + name?: string; value?: unknown; config?: UmbPropertyEditorConfigCollection; mandatory?: boolean; mandatoryMessage?: string; + destroy?: () => void; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/manifests.ts index 14d045bb48..23bb2d577c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/manifests.ts @@ -1,5 +1,5 @@ import { UMB_PROPERTY_TYPE_ENTITY_TYPE, UMB_PROPERTY_TYPE_WORKSPACE_ALIAS } from './constants.js'; -import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; +import { UMB_WORKSPACE_CONDITION_ALIAS, UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { @@ -25,7 +25,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_PROPERTY_TYPE_WORKSPACE_ALIAS, }, ], @@ -43,7 +43,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, oneOf: [UMB_PROPERTY_TYPE_WORKSPACE_ALIAS], }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context.ts index 858c6197e1..b9bcfe9751 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property-type/workspace/property-type-workspace.context.ts @@ -1,6 +1,6 @@ import { UmbPropertyTypeWorkspaceEditorElement } from './property-type-workspace-editor.element.js'; import type { UmbPropertyTypeWorkspaceData } from './property-type-workspace.modal-token.js'; -import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property'; +import type { UmbPropertyDatasetContext, UmbPropertyValueData } from '@umbraco-cms/backoffice/property'; import type { UmbInvariantDatasetWorkspaceContext, UmbRoutableWorkspaceContext, @@ -11,6 +11,7 @@ import { UmbInvariantWorkspacePropertyDatasetContext, UmbWorkspaceIsNewRedirectController, UmbWorkspaceIsNewRedirectControllerAlias, + umbObjectToPropertyValueArray, } from '@umbraco-cms/backoffice/workspace'; import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @@ -18,6 +19,7 @@ import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type' import { UMB_CONTENT_TYPE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/content-type'; import { UmbId } from '@umbraco-cms/backoffice/id'; import { UmbValidationContext } from '@umbraco-cms/backoffice/validation'; +import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs'; export class UmbPropertyTypeWorkspaceContext extends UmbSubmittableWorkspaceContextBase @@ -39,6 +41,13 @@ export class UmbPropertyTypeWorkspaceContext data?.name); readonly unique = this.#data.asObservablePart((data) => data?.id); + readonly values = this.#data.asObservablePart((data) => { + return umbObjectToPropertyValueArray(data); + }); + async getValues(): Promise | undefined> { + return umbObjectToPropertyValueArray(await firstValueFrom(this.data)); + } + constructor(host: UmbControllerHost, args: { manifest: ManifestWorkspace }) { super(host, args.manifest.alias); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-dataset/property-dataset-base-context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-dataset/property-dataset-base-context.ts index d51def11c0..7b7a4c2822 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-dataset/property-dataset-base-context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-dataset/property-dataset-base-context.ts @@ -19,8 +19,13 @@ export class UmbPropertyDatasetContextBase #name = new UmbStringState(undefined); name = this.#name.asObservable(); - #values = new UmbArrayState([], (x) => x.alias); - public readonly values = this.#values.asObservable(); + #properties = new UmbArrayState([], (x) => x.alias); + public readonly properties = this.#properties.asObservable(); + /** + * @deprecated - use `properties` instead. + */ + readonly values = this.properties; + private _entityType!: string; private _unique!: string; @@ -51,12 +56,11 @@ export class UmbPropertyDatasetContextBase /** * @function propertyValueByAlias - * @param {string} propertyAlias - * @returns {Promise | undefined>} - * @description Get an Observable for the value of this property. + * @param {string} propertyAlias - the alias to observe + * @returns {Promise | undefined>} - an Observable for the value of this property. */ async propertyValueByAlias(propertyAlias: string) { - return this.#values.asObservablePart((values) => { + return this.#properties.asObservablePart((values) => { const valueObj = values.find((x) => x.alias === propertyAlias); return valueObj ? (valueObj.value as ReturnType) : undefined; }); @@ -64,26 +68,45 @@ export class UmbPropertyDatasetContextBase /** * @function setPropertyValue - * @param {string} alias + * @param {string} alias - The alias to set this value for * @param {PromiseLike} value - value can be a promise resolving into the actual value or the raw value it self. - * @returns {Promise} * @description Set the value of this property. */ setPropertyValue(alias: string, value: unknown) { - this.#values.appendOne({ alias, value }); + this.#properties.appendOne({ alias, value }); } + /** + * @deprecated Use `getProperties` + * @returns {Array} - Array of properties as objects with alias and value properties. + */ getValues() { - return this.#values.getValue(); + return this.#properties.getValue(); } - setValues(map: Array) { - this.#values.setValue(map); + /** + * @param {Array} properties - Properties array with alias and value properties. + * @deprecated Use `setProperties` + */ + setValues(properties: Array) { + this.#properties.setValue(properties); + } + + /** + * @returns {Array} - Array of properties as objects with alias and value properties. + */ + async getProperties() { + return this.#properties.getValue(); + } + /** + * @param {Array} properties - Properties array with alias and value properties. + */ + setProperties(properties: Array) { + this.#properties.setValue(properties); } /** * Gets the read-only state of the current variant culture. * @returns {*} {boolean} - * @memberof UmbBlockGridInlinePropertyDatasetContext */ getReadOnly(): boolean { return this.#readOnly.getValue(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-dataset/property-dataset-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-dataset/property-dataset-context.interface.ts index c33b0896ed..6bdb7565ef 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-dataset/property-dataset-context.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-dataset/property-dataset-context.interface.ts @@ -1,4 +1,5 @@ import type { UmbVariantId } from '../../variant/variant-id.class.js'; +import type { UmbPropertyValueData } from '../types.js'; import type { UmbContext } from '@umbraco-cms/backoffice/class-api'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; @@ -29,7 +30,9 @@ export interface UmbPropertyDatasetContext extends UmbContext { readonly readOnly: Observable; // Should it be possible to get the properties as a list of property aliases? - //readonly properties: Observable>; + readonly properties: Observable | undefined>; + getProperties(): Promise | undefined>; + //setProperties(properties: Array): void; // Property methods: propertyVariantId?: (propertyAlias: string) => Promise>; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-dataset/property-dataset.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-dataset/property-dataset.element.ts index 04e4cb7c54..e11efdbb81 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property-dataset/property-dataset.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property-dataset/property-dataset.element.ts @@ -96,7 +96,7 @@ export class UmbPropertyDatasetElement extends UmbLitElement { this.observe(this.context.name, this.#observerCallback); // prevent the first change event from firing: this.#allowChangeEvent = false; - this.observe(this.context.values, this.#observerCallback); + this.observe(this.context.properties, this.#observerCallback); } #observerCallback = () => { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/property/property/property.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/property/property/property.element.ts index f54e2dc1e1..1ec36a2abe 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/property/property/property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/property/property/property.element.ts @@ -190,6 +190,9 @@ export class UmbPropertyElement extends UmbLitElement { this.#propertyContext.label, (label) => { this._label = label; + if (this._element) { + this._element.name = label; + } }, null, ); @@ -285,6 +288,7 @@ export class UmbPropertyElement extends UmbLitElement { this.#controlValidator?.destroy(); oldElement?.removeEventListener('change', this._onPropertyEditorChange as any as EventListener); oldElement?.removeEventListener('property-value-change', this._onPropertyEditorChange as any as EventListener); + oldElement?.destroy?.(); this._element = el as ManifestPropertyEditorUi['ELEMENT_TYPE']; @@ -293,8 +297,9 @@ export class UmbPropertyElement extends UmbLitElement { if (this._element) { this._element.addEventListener('change', this._onPropertyEditorChange as any as EventListener); this._element.addEventListener('property-value-change', this._onPropertyEditorChange as any as EventListener); - // No need to observe mandatory, as we already do so and set it on the _element if present: [NL] + // No need to observe mandatory or label, as we already do so and set it on the _element if present: [NL] this._element.mandatory = this._mandatory; + this._element.name = this._label; // No need for a controller alias, as the clean is handled via the observer prop: this.#valueObserver = this.observe( diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/conditions/is-not-trashed/entity-is-not-trashed.condition-config.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/conditions/is-not-trashed/entity-is-not-trashed.condition-config.ts new file mode 100644 index 0000000000..d15c08ade0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/conditions/is-not-trashed/entity-is-not-trashed.condition-config.ts @@ -0,0 +1,12 @@ +import type { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from './constants.js'; +import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface UmbEntityIsNotTrashedConditionConfig + extends UmbConditionConfigBase {} + +declare global { + interface UmbExtensionConditionConfigMap { + UmbEntityIsNotTrashedConditionConfig: UmbEntityIsNotTrashedConditionConfig; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/conditions/is-trashed/entity-is-trashed.condition-config.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/conditions/is-trashed/entity-is-trashed.condition-config.ts new file mode 100644 index 0000000000..9aeff180b4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/conditions/is-trashed/entity-is-trashed.condition-config.ts @@ -0,0 +1,12 @@ +import type { UMB_ENTITY_IS_TRASHED_CONDITION_ALIAS } from './constants.js'; +import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface UmbEntityIsTrashedConditionConfig + extends UmbConditionConfigBase {} + +declare global { + interface UmbExtensionConditionConfigMap { + UmbEntityIsTrashedConditionConfig: UmbEntityIsTrashedConditionConfig; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/conditions/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/conditions/types.ts new file mode 100644 index 0000000000..906a89c23d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/conditions/types.ts @@ -0,0 +1,2 @@ +export type * from './is-not-trashed/entity-is-not-trashed.condition-config.js'; +export type * from './is-trashed/entity-is-trashed.condition-config.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/types.ts index 3a74c7c86f..358d08e0d3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/recycle-bin/types.ts @@ -1,3 +1,5 @@ +export type * from './conditions/types.js'; + export interface UmbRecycleBinRestoreRequestArgs { unique: string; destination: { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/detail-repository-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/detail-repository-base.ts index 5f7d695eaa..12a77e5437 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/detail-repository-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/repository/detail/detail-repository-base.ts @@ -124,7 +124,7 @@ export abstract class UmbDetailRepositoryBase< this.#notificationContext!.peek('positive', notification); } - return { data: model, error }; + return { data: updatedData, error }; } /** diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/index.ts index 7e9361b328..398e7201da 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/index.ts @@ -1,19 +1,6 @@ -import type { ManifestSectionRoute } from './section-route.extension.js'; -import type { ManifestSectionSidebarApp } from './section-sidebar-app.extension.js'; -import type { ManifestSectionView } from './section-view.extension.js'; -import type { ManifestSection } from './section.extension.js'; - export type * from './section-route.extension.js'; export type * from './section-sidebar-app.extension.js'; export type * from './section-view.extension.js'; export type * from './section.extension.js'; export type * from './types.js'; - -type UmbSectionExtensions = ManifestSection | ManifestSectionRoute | ManifestSectionSidebarApp | ManifestSectionView; - -declare global { - interface UmbExtensionManifestMap { - UmbSectionExtensions: UmbSectionExtensions; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/types.ts index 028e21bc99..e4d5de7f8b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/extensions/types.ts @@ -1,3 +1,16 @@ export type * from './section-element.interface.js'; export type * from './section-sidebar-app-element.interface.js'; export type * from './section-view-element.interface.js'; + +import type { ManifestSectionRoute } from './section-route.extension.js'; +import type { ManifestSectionSidebarApp } from './section-sidebar-app.extension.js'; +import type { ManifestSectionView } from './section-view.extension.js'; +import type { ManifestSection } from './section.extension.js'; + +type UmbSectionExtensions = ManifestSection | ManifestSectionRoute | ManifestSectionSidebarApp | ManifestSectionView; + +declare global { + interface UmbExtensionManifestMap { + UmbSectionExtensions: UmbSectionExtensions; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file-manager.class.ts b/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file-manager.class.ts index 227749055d..8e52de540b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file-manager.class.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/temporary-file/temporary-file-manager.class.ts @@ -74,7 +74,9 @@ export class UmbTemporaryFileManager< async #handleUpload(item: UploadableItem) { if (!item.temporaryUnique) throw new Error(`Unique is missing for item ${item}`); - const { error } = await this.#temporaryFileRepository.upload(item.temporaryUnique, item.file); + const { error } = await this.#temporaryFileRepository + .upload(item.temporaryUnique, item.file) + .catch(() => ({ error: true })); let status: TemporaryFileStatus; if (error) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md b/src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md index d034fb6ca4..b5a79df4e0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md @@ -15,7 +15,7 @@ A Validation message consist of a type, path and body. This typically looks like ``` { type: "client", - path: "$.values[?(@.alias = 'my-property-alias')].value", + path: "$.values[?(@.alias == 'my-property-alias')].value", message: "Must contain at least 3 words" } ``` @@ -61,7 +61,7 @@ Data: JsonPath: ``` -"$.values.[?(@.alias = 'my-alias')].value" +"$.values.[?(@.alias == 'my-alias')].value" ``` Paths are based on JSONPath, using JSON Path Queries when looking up data of an Array. Using Queries enables Paths to not point to specific index, but what makes a entry unique. @@ -107,7 +107,7 @@ Such conversation could be from this path: To this path: ``` -"$.values.[?(@.alias = 'my-alias')].value" +"$.values.[?(@.alias == 'my-alias')].value" ``` Once this path is converted to use Json Path Queries, the Data can be changed. The concerned entry might get another index. Without that affecting the accuracy of the path. @@ -135,7 +135,7 @@ The Data Path is a JSON Path defining where the data of this input is located in this.#validationMessageBinder = new UmbBindServerValidationToFormControl( this, this.querySelector('#myInput"), - "$.values.[?(@.alias = 'my-input-alias')].value", + "$.values.[?(@.alias == 'my-input-alias')].value", ); ``` diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts index 9b2857a841..e2951a2b78 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/bind-server-validation-to-form-control.controller.ts @@ -1,11 +1,10 @@ import type { UmbValidationMessage } from '../context/validation-messages.manager.js'; import { UMB_VALIDATION_CONTEXT } from '../context/validation.context-token.js'; import type { UmbFormControlMixinInterface } from '../mixins/form-control.mixin.js'; -import { defaultMemoization } from '@umbraco-cms/backoffice/observable-api'; +import { defaultMemoization, simpleHashCode } from '@umbraco-cms/backoffice/observable-api'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -const ctrlSymbol = Symbol(); const observeSymbol = Symbol(); /** @@ -13,6 +12,7 @@ const observeSymbol = Symbol(); * This controller will add a custom error to the form control if the validation context has any messages for the specified data path. */ export class UmbBindServerValidationToFormControl extends UmbControllerBase { + #context?: typeof UMB_VALIDATION_CONTEXT.TYPE; #control: UmbFormControlMixinInterface; @@ -41,7 +41,7 @@ export class UmbBindServerValidationToFormControl extends UmbControllerBase { } constructor(host: UmbControllerHost, formControl: UmbFormControlMixinInterface, dataPath: string) { - super(host, ctrlSymbol); + super(host,'umbFormControlValidation_'+simpleHashCode(dataPath)); this.#control = formControl; this.consumeContext(UMB_VALIDATION_CONTEXT, (context) => { this.#context = context; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts index 594b65ca45..feaf76897a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/controllers/validation.controller.ts @@ -100,10 +100,10 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal * @example * ```ts * const validationContext = new UmbValidationContext(this); - * validationContext.setDataPath("$.values[?(@.alias='my-property')].value"); + * validationContext.setDataPath("$.values[?(@.alias == 'my-property')].value"); * ``` * - * A message with the path: '$.values[?(@.alias='my-property')].value.innerProperty', will for above example become '$.innerProperty' for the local Validation Context. + * A message with the path: '$.values[?(@.alias == 'my-property')].value.innerProperty', will for above example become '$.innerProperty' for the local Validation Context. */ setDataPath(dataPath: string): void { if (this.#baseDataPath) { @@ -225,8 +225,8 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal this.#validationMode = true; const resultsStatus = await Promise.all(this.#validators.map((v) => v.validate())).then( - () => Promise.resolve(true), - () => Promise.resolve(false), + () => true, + () => false, ); if (!this.messages) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-property-value-query.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-property-value-query.function.ts index d184f6e205..76fb54ce10 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-property-value-query.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-property-value-query.function.ts @@ -3,21 +3,21 @@ import type { UmbVariantPropertyValueModel } from '@umbraco-cms/backoffice/varia /** * Validation Data Path Query generator for Property Value. - * write a JSON-Path filter similar to `?(@.alias = 'myAlias' && @.culture == 'en-us' && @.segment == 'mySegment')` + * write a JSON-Path filter similar to `?(@.alias == 'myAlias' && @.culture == 'en-us' && @.segment == 'mySegment')` * where culture and segment are optional - * @param value - * @returns + * @param {UmbVariantPropertyValueModel} value - the object holding value and alias. + * @returns {string} - a JSON-path query */ export function UmbDataPathPropertyValueQuery( value: UmbPartialSome, 'culture' | 'segment'>, ): string { // write a array of strings for each property, where alias must be present and culture and segment are optional - const filters: Array = [`@.alias = '${value.alias}'`]; + const filters: Array = [`@.alias == '${value.alias}'`]; if (value.culture !== undefined) { - filters.push(`@.culture = ${value.culture ? `'${value.culture}'` : 'null'}`); + filters.push(`@.culture == ${value.culture ? `'${value.culture}'` : 'null'}`); } if (value.segment !== undefined) { - filters.push(`@.segment = ${value.segment ? `'${value.segment}'` : 'null'}`); + filters.push(`@.segment == ${value.segment ? `'${value.segment}'` : 'null'}`); } return `?(${filters.join(' && ')})`; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-variant-query.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-variant-query.function.ts index 25666269cd..175b744992 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-variant-query.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/data-path-variant-query.function.ts @@ -12,9 +12,9 @@ export function UmbDataPathVariantQuery( value: UmbPartialSome, 'segment'>, ): string { // write a array of strings for each property, where culture must be present and segment is optional - const filters: Array = [`@.culture = ${value.culture ? `'${value.culture}'` : 'null'}`]; + const filters: Array = [`@.culture == ${value.culture ? `'${value.culture}'` : 'null'}`]; if (value.segment !== undefined) { - filters.push(`@.segment = ${value.segment ? `'${value.segment}'` : 'null'}`); + filters.push(`@.segment == ${value.segment ? `'${value.segment}'` : 'null'}`); } return `?(${filters.join(' && ')})`; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.function.ts index c7c1244fc0..40ceb3f9da 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.function.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.function.ts @@ -1,9 +1,10 @@ /** * - * @param data - * @param path + * @param {object} data - object to traverse for the value. + * @param {string} path - the JSON path to the value that should be found + * @returns {unknown} - the found value. */ -export function GetValueByJsonPath(data: any, path: string): any { +export function GetValueByJsonPath(data: unknown, path: string): unknown { // strip $ from the path: const strippedPath = path.startsWith('$.') ? path.slice(2) : path; // get value from the path: @@ -12,23 +13,9 @@ export function GetValueByJsonPath(data: any, path: string): any { /** * - * @param path - */ -export function GetPropertyNameFromPath(path: string): string { - // find next '.' or '[' in the path, using regex: - const match = path.match(/\.|\[/); - // If no match is found, we assume its a single key so lets return the value of the key: - if (match === null || match.index === undefined) return path; - - // split the path at the first match: - return path.slice(0, match.index); -} - -/** - * - * @param data - * @param path - * @returns {any} + * @param {object} data - object to traverse for the value. + * @param {string} path - the JSON path to the value that should be found + * @returns {unknown} - the found value. */ function GetNextPropertyValueFromPath(data: any, path: string): any { if (!data) return undefined; @@ -90,8 +77,8 @@ function GetNextPropertyValueFromPath(data: any, path: string): any { } /** - * @param filter - * @returns {Array<(queryFilter: any) => boolean>} - array of methods that returns true if the given items property value matches the value of the query. + * @param {string} filter - A JSON Query, limited to filtering features. Do not support other JSON PATH Query features. + * @returns {Array<(queryFilter: any) => boolean>} - An array of methods that returns true if the given items property value matches the value of the query. */ function JsFilterFromJsonPathFilter(filter: string): Array<(item: any) => boolean> { // strip ?( and ) from the filter @@ -101,7 +88,7 @@ function JsFilterFromJsonPathFilter(filter: string): Array<(item: any) => boolea // map each part to a function that returns true if the part is true return parts.map((part) => { // split the part into key and value - const [path, equal] = part.split(' = '); + const [path, equal] = part.split(' == '); // remove @. const key = path.slice(2); // remove quotes: diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.test.ts b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.test.ts index 3673b28ecd..906463e1e5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/validation/utils/json-path.test.ts @@ -30,7 +30,7 @@ describe('UmbJsonPathFunctions', () => { }); it('query of first entry in an array', () => { - const result = GetValueByJsonPath({ values: [{ id: '123', value: 'test' }] }, "$.values[?(@.id = '123')].value"); + const result = GetValueByJsonPath({ values: [{ id: '123', value: 'test' }] }, "$.values[?(@.id == '123')].value"); expect(result).to.eq('test'); }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/vite.config.ts b/src/Umbraco.Web.UI.Client/src/packages/core/vite.config.ts index a4cb479492..5a0214273d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/vite.config.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/vite.config.ts @@ -23,6 +23,7 @@ export default defineConfig({ 'debug/index': './debug/index.ts', 'entity-action/index': './entity-action/index.ts', 'entity-bulk-action/index': './entity-bulk-action/index.ts', + 'entity-create-option-action/index': './entity-create-option-action/index.ts', 'entity/index': './entity/index.ts', 'entry-point': 'entry-point.ts', 'event/index': './event/index.ts', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-entity-action-menu/workspace-entity-action-menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-entity-action-menu/workspace-entity-action-menu.element.ts index 377cc7cf7f..d7647ae806 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-entity-action-menu/workspace-entity-action-menu.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-entity-action-menu/workspace-entity-action-menu.element.ts @@ -1,16 +1,16 @@ import { UMB_ENTITY_WORKSPACE_CONTEXT } from '../../contexts/index.js'; -import type { UmbWorkspaceUniqueType } from '../../types.js'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, customElement, state, nothing, query } from '@umbraco-cms/backoffice/external/lit'; import type { UmbActionExecutedEvent } from '@umbraco-cms/backoffice/event'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import type { UUIPopoverContainerElement } from '@umbraco-cms/backoffice/external/uui'; +import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; @customElement('umb-workspace-entity-action-menu') export class UmbWorkspaceEntityActionMenuElement extends UmbLitElement { private _workspaceContext?: typeof UMB_ENTITY_WORKSPACE_CONTEXT.TYPE; @state() - private _unique?: UmbWorkspaceUniqueType; + private _unique?: UmbEntityUnique; @state() private _entityType?: string; @@ -39,14 +39,14 @@ export class UmbWorkspaceEntityActionMenuElement extends UmbLitElement { #onActionExecuted(event: UmbActionExecutedEvent) { event.stopPropagation(); - // TODO: This ignorer is just neede for JSON SCHEMA TO WORK, As its not updated with latest TS jet. + // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet. // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this._popover?.hidePopover(); } #onPopoverToggle(event: ToggleEvent) { - // TODO: This ignorer is just neede for JSON SCHEMA TO WORK, As its not updated with latest TS jet. + // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet. // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this._popoverOpen = event.newState === 'open'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts index d7be226d99..f32f98bc68 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts @@ -307,7 +307,9 @@ export class UmbWorkspaceSplitViewVariantSelectorElement extends UmbLitElement { #renderReadOnlyTag(culture?: string | null) { if (!culture) return nothing; - return this.#isReadOnly(culture) ? html`Read-only` : nothing; + return this.#isReadOnly(culture) + ? html`${this.localize.term('general_readOnly')}` + : nothing; } #renderSplitViewButton(variant: UmbDocumentVariantOptionModel) { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/const.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/const.ts index 4f333d6e35..00c36c7222 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/const.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/const.ts @@ -1,2 +1,27 @@ -export const UMB_WORKSPACE_HAS_COLLECTION_CONDITION = 'Umb.Condition.WorkspaceHasCollection'; -export const UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION = 'Umb.Condition.WorkspaceEntityIsNew'; +/** + * Workspace has collection condition alias + */ +export const UMB_WORKSPACE_HAS_COLLECTION_CONDITION_ALIAS = 'Umb.Condition.WorkspaceHasCollection'; + +/** + * [Deperecated] Workspace has collection condition alias + * @deprecated Use {UMB_WORKSPACE_HAS_COLLECTION_CONDITION_ALIAS} instead. This will be removed in Umbraco 16. + */ +export const UMB_WORKSPACE_HAS_COLLECTION_CONDITION = UMB_WORKSPACE_HAS_COLLECTION_CONDITION_ALIAS; + +/** + * Workspace entity is new condition alias + */ +export const UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS = 'Umb.Condition.WorkspaceEntityIsNew'; + +/** + * [Deperecated] Workspace entity is new condition alias + * @deprecated Use {UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS} instead. This will be removed in Umbraco 16. + */ +export const UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION = UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS; + +/** + * Workspace alias condition alias + */ +export const UMB_WORKSPACE_CONDITION_ALIAS = 'Umb.Condition.WorkspaceAlias'; + diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/types.ts index 68f753151a..e13151df82 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/types.ts @@ -1,20 +1,21 @@ -import type { UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION, UMB_WORKSPACE_HAS_COLLECTION_CONDITION } from './const.js'; +import type { UMB_WORKSPACE_CONDITION_ALIAS, UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS, UMB_WORKSPACE_HAS_COLLECTION_CONDITION_ALIAS } from './const.js'; import type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api'; -export type WorkspaceAliasConditionConfig = UmbConditionConfigBase<'Umb.Condition.WorkspaceAlias'> & { - /** - * Define the workspace that this extension should be available in - * @example - * "Umb.Workspace.Document" - */ - match?: string; - /** - * Define one or more workspaces that this extension should be available in - * @example - * ["Umb.Workspace.Document", "Umb.Workspace.Media"] - */ - oneOf?: Array; -}; +export interface WorkspaceAliasConditionConfig + extends UmbConditionConfigBase { + /** + * Define the workspace that this extension should be available in + * @example + * "Umb.Workspace.Document" + */ + match?: string; + /** + * Define one or more workspaces that this extension should be available in + * @example + * ["Umb.Workspace.Document", "Umb.Workspace.Media"] + */ + oneOf?: Array; + } export type WorkspaceContentTypeAliasConditionConfig = UmbConditionConfigBase<'Umb.Condition.WorkspaceContentTypeAlias'> & { @@ -42,11 +43,11 @@ export type WorkspaceEntityTypeConditionConfig = UmbConditionConfigBase<'Umb.Con }; export type WorkspaceHasCollectionConditionConfig = UmbConditionConfigBase< - typeof UMB_WORKSPACE_HAS_COLLECTION_CONDITION + typeof UMB_WORKSPACE_HAS_COLLECTION_CONDITION_ALIAS >; export interface WorkspaceEntityIsNewConditionConfig - extends UmbConditionConfigBase { + extends UmbConditionConfigBase { match: boolean; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias.condition.ts index d327ca6a12..7e6c421ebf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-alias.condition.ts @@ -4,6 +4,7 @@ import type { WorkspaceAliasConditionConfig } from './types.js'; import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from './const.js'; export class UmbWorkspaceAliasCondition extends UmbConditionBase @@ -25,7 +26,7 @@ export class UmbWorkspaceAliasCondition }); } else { throw new Error( - 'Condition `Umb.Condition.WorkspaceAlias` could not be initialized properly. Either "match" or "oneOf" must be defined', + `Condition [UMB_WORKSPACE_CONDITION_ALIAS] (${UMB_WORKSPACE_CONDITION_ALIAS}) could not be initialized properly. Either "match" or "oneOf" must be defined.`, ); } } @@ -34,6 +35,6 @@ export class UmbWorkspaceAliasCondition export const manifest: UmbExtensionManifest = { type: 'condition', name: 'Workspace Alias Condition', - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, api: UmbWorkspaceAliasCondition, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new.condition.ts index c608988944..c3522278c9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-entity-is-new.condition.ts @@ -1,6 +1,6 @@ import { UMB_SUBMITTABLE_WORKSPACE_CONTEXT } from '../index.js'; import type { WorkspaceEntityIsNewConditionConfig } from './types.js'; -import { UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION } from './const.js'; +import { UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS } from './const.js'; import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @@ -32,6 +32,6 @@ export class UmbWorkspaceEntityIsNewCondition export const manifest: UmbExtensionManifest = { type: 'condition', name: 'Workspace Entity Is New Condition', - alias: UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION, + alias: UMB_WORKSPACE_ENTITY_IS_NEW_CONDITION_ALIAS, api: UmbWorkspaceEntityIsNewCondition, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-has-collection.condition.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-has-collection.condition.ts index ea06b69196..9709d9c758 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-has-collection.condition.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/conditions/workspace-has-collection.condition.ts @@ -1,6 +1,6 @@ import { UMB_CONTENT_COLLECTION_WORKSPACE_CONTEXT } from '../../content/collection/content-collection-workspace.context-token.js'; import type { WorkspaceHasCollectionConditionConfig } from './types.js'; -import { UMB_WORKSPACE_HAS_COLLECTION_CONDITION } from './const.js'; +import { UMB_WORKSPACE_HAS_COLLECTION_CONDITION_ALIAS } from './const.js'; import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry'; import type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; @@ -29,6 +29,6 @@ export class UmbWorkspaceHasCollectionCondition export const manifest: UmbExtensionManifest = { type: 'condition', name: 'Workspace Has Collection Condition', - alias: UMB_WORKSPACE_HAS_COLLECTION_CONDITION, + alias: UMB_WORKSPACE_HAS_COLLECTION_CONDITION_ALIAS, api: UmbWorkspaceHasCollectionCondition, }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/contexts/tokens/entity-workspace-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/contexts/tokens/entity-workspace-context.interface.ts index 46fa7a12c7..022b385e73 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/contexts/tokens/entity-workspace-context.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/contexts/tokens/entity-workspace-context.interface.ts @@ -1,8 +1,8 @@ import type { UmbWorkspaceContext } from '../../workspace-context.interface.js'; -import type { UmbWorkspaceUniqueType } from './../../types.js'; +import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; export interface UmbEntityWorkspaceContext extends UmbWorkspaceContext { - unique: Observable; - getUnique(): UmbWorkspaceUniqueType | undefined; + unique: Observable; + getUnique(): UmbEntityUnique | undefined; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/contexts/tokens/invariant-dataset-workspace-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/contexts/tokens/invariant-dataset-workspace-context.interface.ts index faec9d65a3..0ee14795cc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/contexts/tokens/invariant-dataset-workspace-context.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/contexts/tokens/invariant-dataset-workspace-context.interface.ts @@ -1,8 +1,8 @@ -import type { UmbVariantId } from '../../../variant/variant-id.class.js'; -import type { UmbPropertyDatasetContext } from '../../../property/property-dataset/property-dataset-context.interface.js'; import type { UmbSubmittableWorkspaceContext } from './submittable-workspace-context.interface.js'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; +import type { UmbPropertyDatasetContext, UmbPropertyValueData } from '@umbraco-cms/backoffice/property'; +import type { UmbVariantId } from '@umbraco-cms/backoffice/variant'; export interface UmbInvariantDatasetWorkspaceContext extends UmbSubmittableWorkspaceContext { // Name: @@ -10,6 +10,9 @@ export interface UmbInvariantDatasetWorkspaceContext extends UmbSubmittableWorks getName(): string | undefined; setName(name: string): void; + readonly values: Observable | undefined>; + getValues(): Promise | undefined>; + // Property: propertyValueByAlias(alias: string): Promise>; getPropertyValue(alias: string): ReturnType; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/contexts/tokens/property-structure-workspace-context.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/contexts/tokens/property-structure-workspace-context.interface.ts index 566299a425..52ee7e88bf 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/contexts/tokens/property-structure-workspace-context.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/contexts/tokens/property-structure-workspace-context.interface.ts @@ -9,7 +9,7 @@ import type { Observable } from '@umbraco-cms/backoffice/external/rxjs'; export interface UmbPropertyStructureWorkspaceContext< ContentTypeModel extends UmbContentTypeModel = UmbContentTypeModel, > extends UmbEntityWorkspaceContext { - structure: UmbContentTypeStructureManager; + readonly structure: UmbContentTypeStructureManager; // TODO: propertyStructureById is not used by anything in the codebase, should we remove it? [NL] propertyStructureById(id: string): Promise>; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/data-manager/workspace-data-manager.interface.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/data-manager/workspace-data-manager.interface.ts index 568895229a..a07f81ae54 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/data-manager/workspace-data-manager.interface.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/data-manager/workspace-data-manager.interface.ts @@ -1,8 +1,7 @@ import type { UmbController } from '@umbraco-cms/backoffice/controller-api'; -import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; import type { MappingFunction, Observable } from '@umbraco-cms/backoffice/observable-api'; -export interface UmbWorkspaceDataManager extends UmbController { +export interface UmbWorkspaceDataManager extends UmbController { getPersisted(): ModelType | undefined; getCurrent(): ModelType | undefined; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts index 4a85b64773..5a3e30e821 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity-detail/entity-detail-workspace-base.ts @@ -196,6 +196,12 @@ export abstract class UmbEntityDetailWorkspaceContextBase< #onWillNavigate = async (e: CustomEvent) => { const newUrl = e.detail.url; + /* TODO: temp removal of discard changes in workspace modals. + The modal closes before the discard changes dialog is resolved.*/ + if (newUrl.includes('/modal/umb-modal-workspace/')) { + return true; + } + if (this._checkWillNavigateAway(newUrl) && this._data.getHasUnpersistedChanges()) { e.preventDefault(); const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); @@ -244,9 +250,8 @@ export abstract class UmbEntityDetailWorkspaceContextBase< } public override destroy(): void { - this._data.destroy(); - this._detailRepository?.destroy(); window.removeEventListener('willchangestate', this.#onWillNavigate); + this._detailRepository?.destroy(); super.destroy(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity/entity-workspace-data-manager.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity/entity-workspace-data-manager.ts index baceb7e7c8..add9fe42c9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity/entity-workspace-data-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/entity/entity-workspace-data-manager.ts @@ -1,6 +1,5 @@ import type { UmbWorkspaceDataManager } from '../data-manager/workspace-data-manager.interface.js'; import { jsonStringComparison, UmbObjectState, type MappingFunction } from '@umbraco-cms/backoffice/observable-api'; -import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; /** @@ -10,7 +9,7 @@ import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; * @implements {UmbWorkspaceDataManager} * @template ModelType */ -export class UmbEntityWorkspaceDataManager +export class UmbEntityWorkspaceDataManager extends UmbControllerBase implements UmbWorkspaceDataManager { @@ -134,8 +133,11 @@ export class UmbEntityWorkspaceDataManager } override destroy() { - this._persisted.destroy(); - this._current.destroy(); + this._persisted?.destroy(); + this._current?.destroy(); + + (this._persisted as any) = undefined; + (this._current as any) = undefined; super.destroy(); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/index.ts index a568fc8775..32a945865d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/index.ts @@ -1,4 +1,5 @@ export * from './components/index.js'; +export * from './conditions/const.js'; export * from './contexts/index.js'; export * from './controllers/index.js'; export * from './data-manager/index.js'; @@ -7,10 +8,10 @@ export * from './entity/index.js'; export * from './modals/index.js'; export * from './paths.js'; export * from './submittable/index.js'; +export * from './utils/object-to-property-value-array.function.js'; export * from './workspace-context.interface.js'; export * from './workspace-property-dataset/index.js'; export * from './workspace.context-token.js'; export * from './workspace.element.js'; -export * from './workspace.element.js'; export type * from './conditions/index.js'; export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/types.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/types.ts index 93c0e81b21..eae653cf08 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/types.ts @@ -1,2 +1,7 @@ +import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; + export type * from './extensions/types.js'; -export type UmbWorkspaceUniqueType = string | null; +/** + * @deprecated Use `UmbEntityUnique`instead. + */ +export type UmbWorkspaceUniqueType = UmbEntityUnique; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/utils/object-to-property-value-array.function.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/utils/object-to-property-value-array.function.ts new file mode 100644 index 0000000000..67b72045bb --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/utils/object-to-property-value-array.function.ts @@ -0,0 +1,14 @@ +import type { UmbPropertyValueData } from '../../property/types.js'; + +/** + * @function UmbObjectToPropertyValueArray + * @param {object} data - an object with properties to be converted. + * @returns {Array | undefined} - and array of property values or undefined + */ +export function umbObjectToPropertyValueArray(data: object | undefined): Array | undefined { + if (!data) return; + return Object.keys(data).map((key) => ({ + alias: key, + value: (data as any)[key], + })); +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property-dataset/invariant-workspace-property-dataset-context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property-dataset/invariant-workspace-property-dataset-context.ts index 8039e78632..6a5a35038d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property-dataset/invariant-workspace-property-dataset-context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/workspace-property-dataset/invariant-workspace-property-dataset-context.ts @@ -1,10 +1,14 @@ -import type { UmbPropertyDatasetContext, UmbNameablePropertyDatasetContext } from '@umbraco-cms/backoffice/property'; +import type { + UmbPropertyDatasetContext, + UmbNameablePropertyDatasetContext, + UmbPropertyValueData, +} from '@umbraco-cms/backoffice/property'; import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbContextBase } from '@umbraco-cms/backoffice/class-api'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; import type { UmbInvariantDatasetWorkspaceContext } from '@umbraco-cms/backoffice/workspace'; -import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api'; +import { UmbBooleanState, type Observable } from '@umbraco-cms/backoffice/observable-api'; /** * A property dataset context that hooks directly into the workspace context. @@ -47,6 +51,13 @@ export class UmbInvariantWorkspacePropertyDatasetContext< this.name = this.#workspace.name; } + get properties(): Observable | undefined> { + return this.#workspace.values; + } + getProperties(): Promise | undefined> { + return this.#workspace.getValues(); + } + /** * @function propertyValueByAlias * @param {string} propertyAlias @@ -58,9 +69,9 @@ export class UmbInvariantWorkspacePropertyDatasetContext< } /** - * TODO: Write proper JSDocs here. - * @param propertyAlias - * @param value + * @param {string} propertyAlias - The alias of the property + * @param {unknown} value - The value to be set for this property + * @returns {Promise} - an promise which resolves once the value has been set. */ async setPropertyValue(propertyAlias: string, value: unknown) { return this.#workspace.setPropertyValue(propertyAlias, value); diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/index.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/index.ts index 5b2c179b14..c516f24d51 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/index.ts @@ -9,4 +9,4 @@ export * from './paths.js'; export { UmbMoveDataTypeRepository, UMB_MOVE_DATA_TYPE_REPOSITORY_ALIAS } from './entity-actions/move-to/index.js'; -export type { UmbDataTypeDetailModel } from './types.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/menu/manifests.ts index 7fc04fe562..bcd1dda1ba 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/menu/manifests.ts @@ -1,3 +1,5 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; + export const manifests: Array = [ { type: 'menuItem', @@ -19,7 +21,7 @@ export const manifests: Array = [ api: () => import('./data-type-menu-structure.context.js'), conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: 'Umb.Workspace.DataType', }, ], @@ -31,7 +33,7 @@ export const manifests: Array = [ name: 'Data Type Breadcrumb Workspace Footer App', conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: 'Umb.Workspace.DataType', }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-data-type-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-data-type-picker-modal.element.ts index aaf4b0dfdf..b266122c4f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-data-type-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-data-type-picker-modal.element.ts @@ -169,6 +169,7 @@ export class UmbDataTypePickerFlowDataTypePickerModalElement extends UmbModalBas grid-template-rows: 40px 1fr; height: 100%; width: 100%; + word-break: break-word; } .icon { font-size: 2em; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.element.ts index d42bd3f948..44daf00885 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/modals/data-type-picker-flow/data-type-picker-flow-modal.element.ts @@ -448,6 +448,7 @@ export class UmbDataTypePickerFlowModalElement extends UmbModalBaseElement< grid-template-rows: 40px 1fr; height: 100%; width: 100%; + word-break: break-word; } #item-grid .item .icon { diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts index 9e31fd975a..3eb91ed61f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/repository/detail/data-type-detail.server.data-source.ts @@ -1,4 +1,4 @@ -import type { UmbDataTypeDetailModel, UmbDataTypePropertyModel } from '../../types.js'; +import type { UmbDataTypeDetailModel, UmbDataTypePropertyValueModel } from '../../types.js'; import { UMB_DATA_TYPE_ENTITY_TYPE } from '../../entity.js'; import { UmbId } from '@umbraco-cms/backoffice/id'; import type { UmbDetailDataSource } from '@umbraco-cms/backoffice/repository'; @@ -70,7 +70,7 @@ export class UmbDataTypeServerDataSource implements UmbDetailDataSource, + values: data.values as Array, }; return { data: dataType }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/folder/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/folder/workspace/manifests.ts index e1b8108bc0..78447f41ce 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/folder/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/tree/folder/workspace/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DATA_TYPE_FOLDER_ENTITY_TYPE } from '../../../entity.js'; import { UMB_DATA_TYPE_FOLDER_WORKSPACE_ALIAS } from './constants.js'; import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; @@ -26,7 +27,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DATA_TYPE_FOLDER_WORKSPACE_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/types.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/types.ts index 366f7823f3..53c18c6e45 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/types.ts @@ -6,10 +6,10 @@ export interface UmbDataTypeDetailModel { name: string; editorAlias: string | undefined; editorUiAlias: string | null; - values: Array; + values: Array; } -export interface UmbDataTypePropertyModel { +export interface UmbDataTypePropertyValueModel { alias: string; - value: any; + value: ValueType; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts index d2c01bc008..b29b1beac2 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/data-type-workspace.context.ts @@ -1,4 +1,4 @@ -import type { UmbDataTypeDetailModel, UmbDataTypePropertyModel } from '../types.js'; +import type { UmbDataTypeDetailModel, UmbDataTypePropertyValueModel } from '../types.js'; import { type UmbDataTypeDetailRepository, UMB_DATA_TYPE_DETAIL_REPOSITORY_ALIAS } from '../repository/index.js'; import { UMB_DATA_TYPE_ENTITY_TYPE } from '../entity.js'; import { UmbDataTypeWorkspaceEditorElement } from './data-type-workspace-editor.element.js'; @@ -49,6 +49,11 @@ export class UmbDataTypeWorkspaceContext readonly propertyEditorUiAlias = this._data.createObservablePartOfCurrent((data) => data?.editorUiAlias); readonly propertyEditorSchemaAlias = this._data.createObservablePartOfCurrent((data) => data?.editorAlias); + readonly values = this._data.createObservablePartOfCurrent((data) => data?.values); + async getValues() { + return this._data.getCurrent()?.values; + } + #properties = new UmbArrayState([], (x) => x.alias).sortBy( (a, b) => (a.weight || 0) - (b.weight || 0), ); @@ -242,7 +247,7 @@ export class UmbDataTypeWorkspaceContext this.#settingsDefaultData = [ ...this.#propertyEditorSchemaSettingsDefaultData, ...this.#propertyEditorUISettingsDefaultData, - ] satisfies Array; + ] satisfies Array; // We check for satisfied type, because we will be directly transferring them to become value. Future note, if they are not satisfied, we need to transfer alias and value. [NL] this._data.updatePersisted({ values: this.#settingsDefaultData }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/manifests.ts index 06c4007e1e..c83c6cb153 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/workspace/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DATA_TYPE_WORKSPACE_ALIAS } from './constants.js'; import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; @@ -25,7 +26,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DATA_TYPE_WORKSPACE_ALIAS, }, ], @@ -43,7 +44,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DATA_TYPE_WORKSPACE_ALIAS, }, ], @@ -61,7 +62,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DATA_TYPE_WORKSPACE_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/collection/dictionary-collection.element.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/collection/dictionary-collection.element.ts index d4372f22ec..f6eac66c42 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/collection/dictionary-collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/collection/dictionary-collection.element.ts @@ -1,51 +1,18 @@ -import { css, customElement, html } from '@umbraco-cms/backoffice/external/lit'; -import { - UMB_COLLECTION_CONTEXT, - UmbCollectionDefaultElement, - type UmbDefaultCollectionContext, -} from '@umbraco-cms/backoffice/collection'; +import { customElement, html } from '@umbraco-cms/backoffice/external/lit'; +import { UmbCollectionDefaultElement } from '@umbraco-cms/backoffice/collection'; @customElement('umb-dictionary-collection') export class UmbDictionaryCollectionElement extends UmbCollectionDefaultElement { - #collectionContext?: UmbDefaultCollectionContext; - #inputTimer?: NodeJS.Timeout; - #inputTimerAmount = 500; - - constructor() { - super(); - this.consumeContext(UMB_COLLECTION_CONTEXT, (context) => { - this.#collectionContext = context; - }); - } - - #updateSearch(event: InputEvent) { - const target = event.target as HTMLInputElement; - const filter = target.value || ''; - clearTimeout(this.#inputTimer); - this.#inputTimer = setTimeout(() => this.#collectionContext?.setFilter({ filter }), this.#inputTimerAmount); - } - protected override renderToolbar() { - return html`${this.#renderSearch()}`; + return html` + + + + `; } - - #renderSearch() { - return html``; - } - - static override styles = [ - css` - #input-search { - width: 100%; - } - `, - ]; } -export default UmbDictionaryCollectionElement; +export { UmbDictionaryCollectionElement as element }; declare global { interface HTMLElementTagNameMap { diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/collection/index.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/collection/index.ts index 1001b2fa0f..ea8abf649f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/collection/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/collection/index.ts @@ -1,2 +1,4 @@ export { UmbDictionaryCollectionRepository } from './repository/index.js'; export { UMB_DICTIONARY_COLLECTION_ALIAS } from './constants.js'; + +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/menu-item/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/menu-item/manifests.ts index 447eb8727b..f3a87bd76a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/menu-item/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/menu-item/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DICTIONARY_ENTITY_TYPE } from '../entity.js'; import { UMB_DICTIONARY_TREE_ALIAS } from '../tree/index.js'; import { UMB_TRANSLATION_MENU_ALIAS } from '@umbraco-cms/backoffice/translation'; @@ -25,7 +26,7 @@ export const manifests: Array = [ api: () => import('./dictionary-menu-structure.context.js'), conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: 'Umb.Workspace.Dictionary', }, ], @@ -37,7 +38,7 @@ export const manifests: Array = [ name: 'Data Type Breadcrumb Workspace Footer App', conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: 'Umb.Workspace.Dictionary', }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/tree/index.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/tree/index.ts index 0940ddc29a..4f23f5ab53 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/tree/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/tree/index.ts @@ -6,4 +6,5 @@ export { } from './constants.js'; export { UMB_DICTIONARY_TREE_STORE_CONTEXT } from './dictionary-tree.store.js'; export { type UmbDictionaryTreeStore } from './dictionary-tree.store.js'; -export * from './types.js'; + +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/workspace/manifests.ts index a2618139f2..a78e4a8ef3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/workspace/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DICTIONARY_ENTITY_TYPE } from '../entity.js'; import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; @@ -27,7 +28,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DICTIONARY_WORKSPACE_ALIAS, }, ], @@ -46,7 +47,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DICTIONARY_WORKSPACE_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/tree/folder/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/tree/folder/workspace/manifests.ts index af23fb61dd..52f6f4e34f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/tree/folder/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/tree/folder/workspace/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_BLUEPRINT_FOLDER_ENTITY_TYPE } from '../../../entity.js'; import { UMB_DOCUMENT_BLUEPRINT_FOLDER_WORKSPACE_ALIAS } from './constants.js'; import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; @@ -26,7 +27,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DOCUMENT_BLUEPRINT_FOLDER_WORKSPACE_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/types.ts index 0c93761201..5b8d1ac93e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/types.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/types.ts @@ -2,7 +2,7 @@ import type { UmbDocumentBlueprintEntityType } from './entity.js'; import type { UmbEntityVariantModel, UmbEntityVariantOptionModel } from '@umbraco-cms/backoffice/variant'; import type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models'; import { DocumentVariantStateModel as UmbDocumentBlueprintVariantState } from '@umbraco-cms/backoffice/external/backend-api'; -import type { UmbContentValueModel } from '@umbraco-cms/backoffice/content'; +import type { UmbElementValueModel } from '@umbraco-cms/backoffice/content'; export { UmbDocumentBlueprintVariantState }; export interface UmbDocumentBlueprintDetailModel { @@ -27,7 +27,7 @@ export interface UmbDocumentBlueprintUrlInfoModel { } // eslint-disable-next-line @typescript-eslint/no-empty-object-type -export interface UmbDocumentBlueprintValueModel extends UmbContentValueModel {} +export interface UmbDocumentBlueprintValueModel extends UmbElementValueModel {} // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface UmbDocumentBlueprintVariantOptionModel diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/workspace/document-blueprint-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/workspace/document-blueprint-workspace.context.ts index a3959a1686..c3b5b3264f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/workspace/document-blueprint-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/workspace/document-blueprint-workspace.context.ts @@ -84,6 +84,11 @@ export class UmbDocumentBlueprintWorkspaceContext readonly variants = this.#data.createObservablePartOfCurrent((data) => data?.variants || []); + readonly values = this.#data.createObservablePartOfCurrent((data) => data?.values); + getValues() { + return this.#data.getCurrent()?.values; + } + //readonly urls = this.#data.current.asObservablePart((data) => data?.urls || []); readonly structure = new UmbContentTypeStructureManager(this, new UmbDocumentTypeDetailRepository(this)); @@ -465,7 +470,6 @@ export class UmbDocumentBlueprintWorkspaceContext } public override destroy(): void { - this.#data.destroy(); this.structure.destroy(); this.#languageRepository.destroy(); super.destroy(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/workspace/manifests.ts index f7f68a32bf..ba2884d0e4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/workspace/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_BLUEPRINT_ENTITY_TYPE } from '../entity.js'; import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; @@ -27,7 +28,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DOCUMENT_BLUEPRINT_WORKSPACE_ALIAS, }, ], @@ -47,7 +48,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DOCUMENT_BLUEPRINT_WORKSPACE_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/menu/manifests.ts index 2e98f9edd5..73d7506420 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/menu/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_TYPE_TREE_ALIAS } from '../tree/index.js'; export const manifests: Array = [ @@ -20,7 +21,7 @@ export const manifests: Array = [ api: () => import('./document-type-menu-structure.context.js'), conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: 'Umb.Workspace.DocumentType', }, ], @@ -32,7 +33,7 @@ export const manifests: Array = [ name: 'Document Type Breadcrumb Workspace Footer App', conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: 'Umb.Workspace.DocumentType', }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/tree/folder/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/tree/folder/workspace/manifests.ts index 52c211ec2f..d0a5f120c4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/tree/folder/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/tree/folder/workspace/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE } from '../../../entity.js'; import { UMB_DOCUMENT_TYPE_FOLDER_WORKSPACE_ALIAS } from './constants.js'; import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; @@ -26,7 +27,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DOCUMENT_TYPE_FOLDER_WORKSPACE_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/manifests.ts index f4230143a2..028faf033a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/workspace/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_TYPE_COMPOSITION_REPOSITORY_ALIAS } from '../repository/composition/index.js'; import { UmbSubmitWorkspaceAction } from '@umbraco-cms/backoffice/workspace'; @@ -27,7 +28,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS, }, ], @@ -45,7 +46,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS, }, ], @@ -63,7 +64,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS, }, ], @@ -81,7 +82,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS, }, ], @@ -99,7 +100,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DOCUMENT_TYPE_WORKSPACE_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/audit-log/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/audit-log/index.ts index b8d46eff26..8dd29326e4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/audit-log/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/audit-log/index.ts @@ -1 +1,2 @@ export { UmbDocumentAuditLogRepository } from './repository/index.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/document-collection-toolbar.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/document-collection-toolbar.element.ts index e65071e87e..ed675276b4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/document-collection-toolbar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/document-collection-toolbar.element.ts @@ -3,6 +3,7 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UMB_COLLECTION_CONTEXT } from '@umbraco-cms/backoffice/collection'; import type { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection'; +/** @deprecated This component is no longer used in core; to be removed in Umbraco 17. */ @customElement('umb-document-collection-toolbar') export class UmbDocumentCollectionToolbarElement extends UmbLitElement { #collectionContext?: UmbDefaultCollectionContext; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/document-collection.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/document-collection.element.ts index 1befc2e76f..f1bf71c7a6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/document-collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/document-collection.element.ts @@ -1,17 +1,22 @@ -import { html, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { customElement, html } from '@umbraco-cms/backoffice/external/lit'; import { UmbCollectionDefaultElement } from '@umbraco-cms/backoffice/collection'; -import './document-collection-toolbar.element.js'; - @customElement('umb-document-collection') export class UmbDocumentCollectionElement extends UmbCollectionDefaultElement { protected override renderToolbar() { - return html``; + return html` + + + + `; } } +/** @deprecated Should be exported as `element` only; to be removed in Umbraco 17. */ export default UmbDocumentCollectionElement; +export { UmbDocumentCollectionElement as element }; + declare global { interface HTMLElementTagNameMap { 'umb-document-collection': UmbDocumentCollectionElement; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts index 7ade0434a2..05015e1cf8 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/components/input-document/input-document.element.ts @@ -4,10 +4,12 @@ import { css, customElement, html, + ifDefined, nothing, property, repeat, state, + when, } from '@umbraco-cms/backoffice/external/lit'; import { splitStringToArray } from '@umbraco-cms/backoffice/utils'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; @@ -201,12 +203,14 @@ export class UmbInputDocumentElement extends UmbFormControlMixin 0) { return nothing; } else { - return html` `; + return html` + + `; } } @@ -225,16 +229,26 @@ export class UmbInputDocumentElement extends UmbFormControlMixin + name=${item.name} + href=${ifDefined(href)} + ?readonly=${this.readonly} + ?standalone=${this.max === 1}> ${this.#renderIcon(item)} ${this.#renderIsTrashed(item)} - - ${this.#renderOpenButton(item)} ${this.#renderRemoveButton(item)} - + ${when( + !this.readonly, + () => html` + + this.#onRemove(item)}> + + `, + )} `; } @@ -249,25 +263,6 @@ export class UmbInputDocumentElement extends UmbFormControlMixinTrashed`; } - #renderRemoveButton(item: UmbDocumentItemModel) { - if (this.readonly) return nothing; - return html` - this.#onRemove(item)} label=${this.localize.term('general_remove')}> - `; - } - - #renderOpenButton(item: UmbDocumentItemModel) { - if (this.readonly) return nothing; - if (!this.showOpenButton) return nothing; - return html` - - ${this.localize.term('general_open')} - - `; - } - static override styles = [ css` #btn-add { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/document-create-options-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/document-create-options-modal.element.ts index a0eca87a0e..b31709a246 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/document-create-options-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/create/document-create-options-modal.element.ts @@ -176,7 +176,8 @@ export class UmbDocumentCreateOptionsModalElement extends UmbModalBaseElement< .alias=${this.localize.string(documentType.description ?? '')} select-only selectable - @selected=${() => this.#onSelectDocumentType(documentType.unique)}> + @selected=${() => this.#onSelectDocumentType(documentType.unique)} + @open=${() => this.#onSelectDocumentType(documentType.unique)}> `, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts index ff9d645a17..46d3471ea3 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/unpublish.action.ts @@ -77,15 +77,17 @@ export class UmbUnpublishDocumentEntityAction extends UmbEntityActionBase if (variantIds.length) { const publishingRepository = new UmbDocumentPublishingRepository(this._host); - await publishingRepository.unpublish(this.args.unique, variantIds); + const { error } = await publishingRepository.unpublish(this.args.unique, variantIds); - const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadStructureForEntityEvent({ - unique: this.args.unique, - entityType: this.args.entityType, - }); + if (!error) { + const actionEventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); + const event = new UmbRequestReloadStructureForEntityEvent({ + unique: this.args.unique, + entityType: this.args.entityType, + }); - actionEventContext.dispatchEvent(event); + actionEventContext.dispatchEvent(event); + } } } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/menu/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/menu/manifests.ts index 1d1145d447..a842fa686c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/menu/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/menu/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_TREE_ALIAS } from '../tree/index.js'; export const UMB_CONTENT_MENU_ALIAS = 'Umb.Menu.Content'; @@ -28,7 +29,7 @@ export const manifests: Array = [ api: () => import('./document-menu-structure.context.js'), conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: 'Umb.Workspace.Document', }, ], @@ -40,7 +41,7 @@ export const manifests: Array = [ name: 'Document Breadcrumb Workspace Footer App', conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: 'Umb.Workspace.Document', }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/index.ts new file mode 100644 index 0000000000..6585fe3792 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/property-dataset-context/index.ts @@ -0,0 +1 @@ +export * from './document-property-dataset-context.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/recycle-bin/repository/types.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts index 46708951d5..21eb2be780 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/detail/document-detail.server.data-source.ts @@ -42,6 +42,7 @@ export class UmbDocumentServerDataSource implements UmbDetailDataSource = [...detailManifests, ...itemManifests, ...publishingManifests]; +export const manifests: Array = [ + ...detailManifests, + ...itemManifests, + ...publishingManifests, + ...urlManifests, +]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/types.ts new file mode 100644 index 0000000000..204e345ff9 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/types.ts @@ -0,0 +1 @@ +export type { UmbDocumentItemModel } from './item/types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/constants.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/constants.ts new file mode 100644 index 0000000000..ba77e1ff72 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/constants.ts @@ -0,0 +1,2 @@ +export const UMB_DOCUMENT_URL_REPOSITORY_ALIAS = 'Umb.Repository.Document.Url'; +export const UMB_DOCUMENT_URL_STORE_ALIAS = 'Umb.Store.Document.Url'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/document-url.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/document-url.repository.ts new file mode 100644 index 0000000000..6b65fe3545 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/document-url.repository.ts @@ -0,0 +1,13 @@ +import type { UmbDocumentUrlsModel } from './types.js'; +import { UMB_DOCUMENT_URL_STORE_CONTEXT } from './document-url.store.context-token.js'; +import { UmbDocumentUrlServerDataSource } from './document-url.server.data-source.js'; +import { UmbItemRepositoryBase } from '@umbraco-cms/backoffice/repository'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +export class UmbDocumentUrlRepository extends UmbItemRepositoryBase { + constructor(host: UmbControllerHost) { + super(host, UmbDocumentUrlServerDataSource, UMB_DOCUMENT_URL_STORE_CONTEXT); + } +} + +export { UmbDocumentUrlRepository as api }; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/document-url.server.data-source.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/document-url.server.data-source.ts new file mode 100644 index 0000000000..2993e37a40 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/document-url.server.data-source.ts @@ -0,0 +1,29 @@ +import type { UmbDocumentUrlsModel } from './types.js'; +import { DocumentService } from '@umbraco-cms/backoffice/external/backend-api'; +import { UmbItemServerDataSourceBase } from '@umbraco-cms/backoffice/repository'; +import type { DocumentUrlInfoResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; + +/** + * A server data source for Document URLs + * @class UmbDocumentUrlServerDataSource + * @implements {DocumentTreeDataSource} + */ +export class UmbDocumentUrlServerDataSource extends UmbItemServerDataSourceBase< + DocumentUrlInfoResponseModel, + UmbDocumentUrlsModel +> { + /** + * Creates an instance of UmbDocumentUrlServerDataSource. + * @param {UmbControllerHost} host - The controller host for this controller to be appended to + * @memberof UmbDocumentUrlServerDataSource + */ + constructor(host: UmbControllerHost) { + super(host, { getItems, mapper }); + } +} + +/* eslint-disable local-rules/no-direct-api-import */ +const getItems = (uniques: Array) => DocumentService.getDocumentUrls({ id: uniques }); + +const mapper = (item: DocumentUrlInfoResponseModel): UmbDocumentUrlsModel => ({ unique: item.id, urls: item.urlInfos }); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/document-url.store.context-token.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/document-url.store.context-token.ts new file mode 100644 index 0000000000..f2ac89657f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/document-url.store.context-token.ts @@ -0,0 +1,4 @@ +import type UmbDocumentUrlStore from './document-url.store.js'; +import { UmbContextToken } from '@umbraco-cms/backoffice/context-api'; + +export const UMB_DOCUMENT_URL_STORE_CONTEXT = new UmbContextToken('UmbDocumentUrlStore'); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/document-url.store.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/document-url.store.ts new file mode 100644 index 0000000000..3f5a9f514e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/document-url.store.ts @@ -0,0 +1,23 @@ +import type { UmbDocumentDetailModel } from '../../types.js'; +import { UMB_DOCUMENT_URL_STORE_CONTEXT } from './document-url.store.context-token.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbItemStoreBase } from '@umbraco-cms/backoffice/store'; + +/** + * @class UmbDocumentUrlStore + * @augments {UmbStoreBase} + * @description - Data Store for Document URLs + */ + +export class UmbDocumentUrlStore extends UmbItemStoreBase { + /** + * Creates an instance of UmbDocumentUrlStore. + * @param {UmbControllerHost} host - The controller host for this controller to be appended to + * @memberof UmbDocumentUrlStore + */ + constructor(host: UmbControllerHost) { + super(host, UMB_DOCUMENT_URL_STORE_CONTEXT.toString()); + } +} + +export default UmbDocumentUrlStore; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/index.ts new file mode 100644 index 0000000000..f5bc60dacd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/index.ts @@ -0,0 +1,2 @@ +export { UmbDocumentUrlRepository } from './document-url.repository.js'; +export { UMB_DOCUMENT_URL_REPOSITORY_ALIAS } from './constants.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/manifests.ts new file mode 100644 index 0000000000..d4545015b2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/manifests.ts @@ -0,0 +1,18 @@ +import { UMB_DOCUMENT_URL_REPOSITORY_ALIAS, UMB_DOCUMENT_URL_STORE_ALIAS } from './constants.js'; +import type { ManifestItemStore, ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; + +const urlRepository: ManifestRepository = { + type: 'repository', + alias: UMB_DOCUMENT_URL_REPOSITORY_ALIAS, + name: 'Document Url Repository', + api: () => import('./document-url.repository.js'), +}; + +const urlStore: ManifestItemStore = { + type: 'itemStore', + alias: UMB_DOCUMENT_URL_STORE_ALIAS, + name: 'Document Url Store', + api: () => import('./document-url.store.js'), +}; + +export const manifests = [urlRepository, urlStore]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/types.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/types.ts new file mode 100644 index 0000000000..7291179f31 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/repository/url/types.ts @@ -0,0 +1,9 @@ +export interface UmbDocumentUrlsModel { + unique: string; + urls: Array; +} + +export interface UmbDocumentUrlModel { + culture?: string | null; + url?: string; +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/rollback/entity-action/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/rollback/entity-action/manifests.ts index 88dcf25c5c..2bed9b0321 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/rollback/entity-action/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/rollback/entity-action/manifests.ts @@ -1,3 +1,4 @@ +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; import { UMB_USER_PERMISSION_DOCUMENT_ROLLBACK } from '../../user-permissions/index.js'; import { UMB_DOCUMENT_WORKSPACE_ALIAS } from '../../workspace/index.js'; @@ -27,7 +28,7 @@ export const manifests: Array = [ /* Currently the rollback is tightly coupled to the workspace contexts so we only allow it to show up In the document workspace. */ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DOCUMENT_WORKSPACE_ALIAS, }, ], diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/index.ts index db48eed3f7..628bc2c8da 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/index.ts @@ -6,4 +6,4 @@ export { } from './manifests.js'; export { UMB_DOCUMENT_TREE_STORE_CONTEXT } from './document-tree.store.context-token.js'; export type { UmbDocumentTreeStore } from './document-tree.store.js'; -export * from './types.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/tree-item/document-tree-item.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/tree-item/document-tree-item.element.ts index 3c5c94c536..634f3d8bad 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/tree-item/document-tree-item.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/tree/tree-item/document-tree-item.element.ts @@ -1,4 +1,5 @@ import type { UmbDocumentTreeItemModel, UmbDocumentTreeItemVariantModel } from '../types.js'; +import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; import { css, html, nothing, customElement, state, classMap } from '@umbraco-cms/backoffice/external/lit'; import type { UmbAppLanguageContext } from '@umbraco-cms/backoffice/language'; import { UMB_APP_LANGUAGE_CONTEXT } from '@umbraco-cms/backoffice/language'; @@ -31,7 +32,7 @@ export class UmbDocumentTreeItemElement extends UmbTreeItemElementBase { this._currentCulture = value; - this._variant = this.#getVariant(value); + this._variant = this.#findVariant(value); }); } @@ -41,7 +42,7 @@ export class UmbDocumentTreeItemElement extends UmbTreeItemElementBase x.culture === culture); } @@ -56,16 +57,22 @@ export class UmbDocumentTreeItemElement extends UmbTreeItemElementBase extends UmbContentValueModel {} +export interface UmbDocumentValueModel extends UmbElementValueModel {} // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface UmbDocumentVariantOptionModel extends UmbEntityVariantOptionModel {} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/index.ts index 4991633288..3ed3c5c183 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/user-permissions/index.ts @@ -1,2 +1,3 @@ export * from './repository/index.js'; export * from './constants.js'; +export type * from './types.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts index 9766f8261f..4c21724833 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/document-workspace.context.ts @@ -75,6 +75,7 @@ import { UmbIsTrashedEntityContext } from '@umbraco-cms/backoffice/recycle-bin'; import { UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; import { UmbDataTypeItemRepositoryManager } from '@umbraco-cms/backoffice/data-type'; import type { UmbRepositoryResponse } from '@umbraco-cms/backoffice/repository'; +import { UMB_APP_CONTEXT } from '@umbraco-cms/backoffice/app'; type EntityModel = UmbDocumentDetailModel; type EntityTypeModel = UmbDocumentTypeDetailModel; @@ -120,6 +121,10 @@ export class UmbDocumentWorkspaceContext readonly unique = this.#data.createObservablePartOfCurrent((data) => data?.unique); readonly entityType = this.#data.createObservablePartOfCurrent((data) => data?.entityType); readonly isTrashed = this.#data.createObservablePartOfCurrent((data) => data?.isTrashed); + readonly values = this.#data.createObservablePartOfCurrent((data) => data?.values); + getValues() { + return this.#data.getCurrent()?.values; + } readonly contentTypeUnique = this.#data.createObservablePartOfCurrent((data) => data?.documentType.unique); readonly contentTypeHasCollection = this.#data.createObservablePartOfCurrent( @@ -287,6 +292,7 @@ export class UmbDocumentWorkspaceContext override resetState() { super.resetState(); this.#data.clear(); + this.removeUmbControllerByAlias(UmbWorkspaceIsNewRedirectControllerAlias); } async loadLanguages() { @@ -298,6 +304,7 @@ export class UmbDocumentWorkspaceContext async load(unique: string) { this.resetState(); this.#getDataPromise = this.repository.requestByUnique(unique); + type GetDataType = Awaited>; const { data, asObservable } = (await this.#getDataPromise) as GetDataType; @@ -598,7 +605,9 @@ export class UmbDocumentWorkspaceContext // Tell the server that we're entering preview mode. await new UmbDocumentPreviewRepository(this).enter(); - const previewUrl = new URL('preview', window.location.origin); + const appContext = await this.getContext(UMB_APP_CONTEXT); + + const previewUrl = new URL(appContext.getBackofficePath() + '/preview', appContext.getServerUrl()); previewUrl.searchParams.set('id', unique); if (culture && culture !== UMB_INVARIANT_CULTURE) { @@ -707,18 +716,20 @@ export class UmbDocumentWorkspaceContext await this.#performSaveOrCreate(variantIds, saveData); - await this.publishingRepository.publish( + const { error } = await this.publishingRepository.publish( unique, variantIds.map((variantId) => ({ variantId })), ); - const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); - const event = new UmbRequestReloadStructureForEntityEvent({ - unique: this.getUnique()!, - entityType: this.getEntityType(), - }); + if (!error) { + const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); + const event = new UmbRequestReloadStructureForEntityEvent({ + unique: this.getUnique()!, + entityType: this.getEntityType(), + }); - eventContext.dispatchEvent(event); + eventContext.dispatchEvent(event); + } } async #handleSave() { @@ -823,6 +834,12 @@ export class UmbDocumentWorkspaceContext } public async publishWithDescendants() { + const unique = this.getUnique(); + if (!unique) throw new Error('Unique is missing'); + + const entityType = this.getEntityType(); + if (!entityType) throw new Error('Entity type is missing'); + const { options, selected } = await this.#determineVariantOptions(); const modalManagerContext = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); @@ -844,15 +861,30 @@ export class UmbDocumentWorkspaceContext if (!variantIds.length) return; - // TODO: Validate content & Save changes for the selected variants — This was how it worked in v.13 [NL] - - const unique = this.getUnique(); - if (!unique) throw new Error('Unique is missing'); - await this.publishingRepository.publishWithDescendants( + const { error } = await this.publishingRepository.publishWithDescendants( unique, variantIds, result.includeUnpublishedDescendants ?? false, ); + + if (!error) { + const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT); + + // request reload of this entity + const structureEvent = new UmbRequestReloadStructureForEntityEvent({ + entityType, + unique, + }); + + // request reload of the children + const childrenEvent = new UmbRequestReloadChildrenOfEntityEvent({ + entityType, + unique, + }); + + eventContext.dispatchEvent(structureEvent); + eventContext.dispatchEvent(childrenEvent); + } } async delete() { @@ -870,7 +902,6 @@ export class UmbDocumentWorkspaceContext } public override destroy(): void { - this.#data.destroy(); this.structure.destroy(); this.#languageRepository.destroy(); super.destroy(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/manifests.ts index 0301ab190c..a79c270056 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/manifests.ts @@ -7,6 +7,7 @@ import { UMB_DOCUMENT_ENTITY_TYPE } from '../entity.js'; import { UMB_DOCUMENT_WORKSPACE_ALIAS } from './constants.js'; import { UMB_ENTITY_IS_NOT_TRASHED_CONDITION_ALIAS } from '@umbraco-cms/backoffice/recycle-bin'; import { UMB_CONTENT_HAS_PROPERTIES_WORKSPACE_CONDITION } from '@umbraco-cms/backoffice/content'; +import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ { @@ -31,7 +32,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DOCUMENT_WORKSPACE_ALIAS, }, { @@ -52,7 +53,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DOCUMENT_WORKSPACE_ALIAS, }, { @@ -73,7 +74,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DOCUMENT_WORKSPACE_ALIAS, }, ], @@ -92,7 +93,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DOCUMENT_WORKSPACE_ALIAS, }, { @@ -114,7 +115,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DOCUMENT_WORKSPACE_ALIAS, }, { @@ -134,7 +135,7 @@ export const manifests: Array = [ }, conditions: [ { - alias: 'Umb.Condition.WorkspaceAlias', + alias: UMB_WORKSPACE_CONDITION_ALIAS, match: UMB_DOCUMENT_WORKSPACE_ALIAS, }, { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-history.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-history.element.ts index 889c24988a..cfa0bc151a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-history.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-history.element.ts @@ -1,32 +1,40 @@ import type { UmbDocumentAuditLogModel } from '../../../audit-log/types.js'; import { UmbDocumentAuditLogRepository } from '../../../audit-log/index.js'; import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '../../document-workspace.context-token.js'; -import { TimeOptions, getDocumentHistoryTagStyleAndText } from './utils.js'; -import { css, html, customElement, state, nothing, repeat } from '@umbraco-cms/backoffice/external/lit'; +import { getDocumentHistoryTagStyleAndText, TimeOptions } from './utils.js'; +import { css, customElement, html, nothing, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbPaginationManager } from '@umbraco-cms/backoffice/utils'; -import type { UUIPaginationEvent } from '@umbraco-cms/backoffice/external/uui'; -import type { UmbUserItemModel } from '@umbraco-cms/backoffice/user'; +import { UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbUserItemRepository } from '@umbraco-cms/backoffice/user'; +import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; +import type { ManifestEntityAction } from '@umbraco-cms/backoffice/entity-action'; +import type { UmbUserItemModel } from '@umbraco-cms/backoffice/user'; +import type { UUIPaginationEvent } from '@umbraco-cms/backoffice/external/uui'; @customElement('umb-document-workspace-view-info-history') export class UmbDocumentWorkspaceViewInfoHistoryElement extends UmbLitElement { - @state() - _currentPageNumber = 1; + #allowedActions = new Set(['Umb.EntityAction.Document.Rollback']); + + #auditLogRepository = new UmbDocumentAuditLogRepository(this); + + #pagination = new UmbPaginationManager(); + + #userItemRepository = new UmbUserItemRepository(this); + + #userMap = new Map(); + + #workspaceContext?: typeof UMB_DOCUMENT_WORKSPACE_CONTEXT.TYPE; @state() - _totalPages = 1; + private _currentPageNumber = 1; @state() private _items: Array = []; - #workspaceContext?: typeof UMB_DOCUMENT_WORKSPACE_CONTEXT.TYPE; - #auditLogRepository = new UmbDocumentAuditLogRepository(this); - #pagination = new UmbPaginationManager(); - #userItemRepository = new UmbUserItemRepository(this); - - #userMap = new Map(); + @state() + private _totalPages = 1; constructor() { super(); @@ -35,12 +43,23 @@ export class UmbDocumentWorkspaceViewInfoHistoryElement extends UmbLitElement { this.observe(this.#pagination.currentPage, (number) => (this._currentPageNumber = number)); this.observe(this.#pagination.totalPages, (number) => (this._totalPages = number)); + this.consumeContext(UMB_ACTION_EVENT_CONTEXT, (context) => { + context.addEventListener(UmbRequestReloadStructureForEntityEvent.TYPE, () => { + this.#requestAuditLogs(); + }); + }); + this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (instance) => { this.#workspaceContext = instance; this.#requestAuditLogs(); }); } + #onPageChange(event: UUIPaginationEvent) { + this.#pagination.setCurrentPageNumber(event.target?.current); + this.#requestAuditLogs(); + } + async #requestAuditLogs() { const unique = this.#workspaceContext?.getUnique(); if (!unique) throw new Error('Document unique is required'); @@ -58,11 +77,6 @@ export class UmbDocumentWorkspaceViewInfoHistoryElement extends UmbLitElement { } } - #onPageChange(event: UUIPaginationEvent) { - this.#pagination.setCurrentPageNumber(event.target?.current); - this.#requestAuditLogs(); - } - async #requestAndCacheUserItems() { const allUsers = this._items?.map((item) => item.user.unique).filter(Boolean) as string[]; const uniqueUsers = [...new Set(allUsers)]; @@ -83,90 +97,84 @@ export class UmbDocumentWorkspaceViewInfoHistoryElement extends UmbLitElement { } override render() { - return html` - History - + manifest.alias === 'Umb.EntityAction.Document.Rollback'}> + .filter=${(manifest: ManifestEntityAction) => this.#allowedActions.has(manifest.alias)}> - ${this._items ? this.#renderHistory() : html` `} + ${when( + this._items, + () => this.#renderHistory(), + () => html`
`, + )} ${this.#renderPagination()} -
`; + + `; } #renderHistory() { - if (this._items && this._items.length) { - return html` - - ${repeat( - this._items, - (item) => item.timestamp, - (item) => { - const { text, style } = getDocumentHistoryTagStyleAndText(item.logType); - const user = this.#userMap.get(item.user.unique); + if (!this._items?.length) return html`${this.localize.term('content_noItemsToShow')}`; + return html` + + ${repeat( + this._items, + (item) => item.timestamp, + (item) => { + const { text, style } = getDocumentHistoryTagStyleAndText(item.logType); + const user = this.#userMap.get(item.user.unique); - return html` - - + .imgUrls=${user?.avatarUrls ?? []}> + +
${this.localize.term(text.label, item.parameters)} - ${this.localize.term(text.desc, item.parameters)} - - `; - }, - )} - - `; - } else { - return html`${this.localize.term('content_noItemsToShow')}`; - } + ${this.localize.term(text.desc, item.parameters)} +
+
+ `; + }, + )} +
+ `; } #renderPagination() { + if (this._totalPages <= 1) return nothing; return html` - ${this._totalPages > 1 - ? html` - - ` - : nothing} + `; } static override styles = [ UmbTextStyles, css` - uui-loader-circle { - font-size: 2rem; - } - - uui-tag uui-icon { - margin-right: var(--uui-size-space-1); + #loader { + display: flex; + justify-content: center; } .log-type { - flex-grow: 1; - gap: var(--uui-size-space-2); + display: grid; + grid-template-columns: var(--uui-size-40) auto; + gap: var(--uui-size-layout-1); } uui-pagination { flex: 1; - display: inline-block; - } - - .pagination { display: flex; justify-content: center; margin-top: var(--uui-size-layout-1); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-links.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-links.element.ts new file mode 100644 index 0000000000..f244371dfa --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-links.element.ts @@ -0,0 +1,178 @@ +import { UmbDocumentUrlRepository } from '../../../repository/url/document-url.repository.js'; +import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '../../document-workspace.context-token.js'; +import type { UmbDocumentVariantOptionModel } from '../../../types.js'; +import { css, customElement, html, map, nothing, state, when } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbRequestReloadStructureForEntityEvent } from '@umbraco-cms/backoffice/entity-action'; +import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action'; +import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; +import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; + +@customElement('umb-document-workspace-view-info-links') +export class UmbDocumentWorkspaceViewInfoLinksElement extends UmbLitElement { + #documentUrlRepository = new UmbDocumentUrlRepository(this); + + @state() + private _isNew = false; + + @state() + private _unique?: string; + + @state() + private _variantOptions?: Array; + + @state() + private _lookup: Record = {}; + + constructor() { + super(); + + this.consumeContext(UMB_ACTION_EVENT_CONTEXT, (context) => { + context.addEventListener(UmbRequestReloadStructureForEntityEvent.TYPE, () => { + this.#requestUrls(); + }); + }); + + this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (context) => { + this.observe( + observeMultiple([context.isNew, context.unique, context.variantOptions]), + ([isNew, unique, variantOptions]) => { + this._isNew = isNew === true; + this._unique = unique; + this._variantOptions = variantOptions; + this.#requestUrls(); + }, + ); + }); + } + + async #requestUrls() { + if (this._isNew) return; + + if (!this._unique) throw new Error('Document unique is required'); + + const { data } = await this.#documentUrlRepository.requestItems([this._unique]); + + if (data?.length) { + data[0].urls.forEach((item) => { + if (item.culture && item.url) { + this._lookup[item.culture] = item.url; + } + }); + this.requestUpdate('_lookup'); + } + } + + #getStateLocalizationKey(variantOption: UmbDocumentVariantOptionModel) { + switch (variantOption.variant?.state) { + case null: + case undefined: + case DocumentVariantStateModel.NOT_CREATED: + return 'content_notCreated'; + case DocumentVariantStateModel.DRAFT: + return 'content_itemNotPublished'; + case DocumentVariantStateModel.PUBLISHED: + return 'content_routeErrorCannotRoute'; + default: + return 'content_parentNotPublishedAnomaly'; + } + } + + override render() { + return html` + + ${when( + this._isNew, + () => this.#renderNotCreated(), + () => this.#renderUrls(), + )} + + `; + } + + #renderNotCreated() { + return html` + + `; + } + + #renderUrls() { + if (!this._variantOptions?.length) return nothing; + return map(this._variantOptions, (variantOption) => this.#renderUrl(variantOption)); + } + + #renderUrl(variantOption: UmbDocumentVariantOptionModel) { + const varies = !!variantOption.culture; + const culture = varies ? variantOption.culture! : variantOption.language.unique; + const url = this._lookup[culture]; + return when( + url, + () => html` + + + ${varies ? culture : nothing} + ${url} + + + + `, + () => html` + + `, + ); + } + + static override styles = [ + css` + uui-box { + --uui-box-default-padding: 0; + } + + .link-item { + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--uui-size-6); + + padding: var(--uui-size-space-4) var(--uui-size-space-6); + + &:is(a) { + cursor: pointer; + color: inherit; + text-decoration: none; + } + + &:is(a):hover { + background: var(--uui-color-divider); + } + + & > span { + display: flex; + align-items: center; + gap: var(--uui-size-6); + } + + .culture { + color: var(--uui-color-divider-emphasis); + } + } + `, + ]; +} + +export default UmbDocumentWorkspaceViewInfoLinksElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-document-workspace-view-info-links': UmbDocumentWorkspaceViewInfoLinksElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-reference.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-reference.element.ts index 28ad1b37ae..12cccd21f1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-reference.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info-reference.element.ts @@ -1,21 +1,18 @@ import { UmbDocumentReferenceRepository } from '../../../reference/index.js'; -import { css, html, customElement, state, nothing, repeat, property } from '@umbraco-cms/backoffice/external/lit'; -import type { UUIPaginationEvent } from '@umbraco-cms/backoffice/external/uui'; +import { css, customElement, html, nothing, property, repeat, state, when } from '@umbraco-cms/backoffice/external/lit'; +import { isDefaultReference, isDocumentReference, isMediaReference } from '@umbraco-cms/backoffice/relations'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; -import { - isDefaultReference, - isDocumentReference, - isMediaReference, - type UmbReferenceModel, -} from '@umbraco-cms/backoffice/relations'; +import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; +import type { UmbReferenceModel } from '@umbraco-cms/backoffice/relations'; +import type { UUIPaginationEvent } from '@umbraco-cms/backoffice/external/uui'; @customElement('umb-document-workspace-view-info-reference') export class UmbDocumentWorkspaceViewInfoReferenceElement extends UmbLitElement { #itemsPerPage = 10; - #referenceRepository; + + #referenceRepository = new UmbDocumentReferenceRepository(this); @property() documentUnique = ''; @@ -34,7 +31,6 @@ export class UmbDocumentWorkspaceViewInfoReferenceElement extends UmbLitElement constructor() { super(); - this.#referenceRepository = new UmbDocumentReferenceRepository(this); new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) .addAdditionalPath('document') @@ -113,63 +109,65 @@ export class UmbDocumentWorkspaceViewInfoReferenceElement extends UmbLitElement } override render() { - if (this._items && this._items.length > 0) { - return html` - - - - Name - Status - Type Name - Type - - - ${repeat( - this._items, - (item) => item.id, - (item) => - html` - - - - - ${isDocumentReference(item) - ? html` - ${item.name} - ` - : item.name} - - - ${this.#getPublishedStatus(item) - ? this.localize.term('content_published') - : this.localize.term('content_unpublished')} - - ${this.#getContentTypeName(item)} - ${this.#getContentType(item)} - `, - )} - - - ${this.#renderReferencePagination()}`; - } else { - return nothing; - } + if (!this._items?.length) return nothing; + return html` + + + + + Name + Status + Type Name + Type + + ${repeat( + this._items, + (item) => item.id, + (item) => html` + + + + + + ${when( + isDocumentReference(item), + () => html` + + ${item.name} + + `, + () => item.name, + )} + + + ${this.#getPublishedStatus(item) + ? this.localize.term('content_published') + : this.localize.term('content_unpublished')} + + ${this.#getContentTypeName(item)} + ${this.#getContentType(item)} + + `, + )} + + + ${this.#renderReferencePagination()} + `; } #renderReferencePagination() { if (!this._total) return nothing; const totalPages = Math.ceil(this._total / this.#itemsPerPage); - if (totalPages <= 1) return nothing; - return html``; + return html` + + `; } static override styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info.element.ts index 3a0eee8dc7..576f954bbc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/workspace/views/info/document-workspace-view-info.element.ts @@ -1,39 +1,30 @@ +import { UMB_DOCUMENT_PROPERTY_DATASET_CONTEXT } from '../../../property-dataset-context/index.js'; +import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '../../document-workspace.context-token.js'; +import type { UmbDocumentVariantModel } from '../../../types.js'; import { TimeOptions } from './utils.js'; -import { css, customElement, html, ifDefined, repeat, state } from '@umbraco-cms/backoffice/external/lit'; +import { css, customElement, html, ifDefined, nothing, state } from '@umbraco-cms/backoffice/external/lit'; import { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/router'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/document'; import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { UMB_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/workspace'; import { UMB_TEMPLATE_PICKER_MODAL, UmbTemplateItemRepository } from '@umbraco-cms/backoffice/template'; -import type { DocumentUrlInfoModel } from '@umbraco-cms/backoffice/external/backend-api'; import type { UmbDocumentTypeDetailModel } from '@umbraco-cms/backoffice/document-type'; -import type { UmbDocumentVariantModel } from '@umbraco-cms/backoffice/document'; import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router'; // import of local components +import './document-workspace-view-info-links.element.js'; import './document-workspace-view-info-history.element.js'; import './document-workspace-view-info-reference.element.js'; @customElement('umb-document-workspace-view-info') export class UmbDocumentWorkspaceViewInfoElement extends UmbLitElement { - @state() - private _invariantCulture = 'en-US'; - @state() private _documentUnique = ''; + // Document Type @state() - private _urls?: Array; - - @state() - private _createDate?: string; - - /**Document Type */ - @state() - private _documentTypeUnique = ''; + private _documentTypeUnique?: string = ''; @state() private _documentTypeName?: string; @@ -44,7 +35,7 @@ export class UmbDocumentWorkspaceViewInfoElement extends UmbLitElement { @state() private _allowedTemplates?: UmbDocumentTypeDetailModel['allowedTemplates']; - /**Template */ + // Template @state() private _templateUnique = ''; @@ -52,7 +43,7 @@ export class UmbDocumentWorkspaceViewInfoElement extends UmbLitElement { private _templateName?: string; @state() - private _variants: UmbDocumentVariantModel[] = []; + private _variant?: UmbDocumentVariantModel; #workspaceContext?: typeof UMB_DOCUMENT_WORKSPACE_CONTEXT.TYPE; @@ -78,6 +69,12 @@ export class UmbDocumentWorkspaceViewInfoElement extends UmbLitElement { this._documentTypeUnique = this.#workspaceContext.getContentTypeId()!; this.#observeContent(); }); + + this.consumeContext(UMB_DOCUMENT_PROPERTY_DATASET_CONTEXT, (context) => { + this.observe(context.currentVariant, (currentVariant) => { + this._variant = currentVariant; + }); + }); } #observeContent() { @@ -93,14 +90,6 @@ export class UmbDocumentWorkspaceViewInfoElement extends UmbLitElement { '_documentType', ); - this.observe( - this.#workspaceContext.urls, - (urls) => { - this._urls = urls; - }, - '_documentUrls', - ); - this.observe( this.#workspaceContext.unique, (unique) => { @@ -121,44 +110,10 @@ export class UmbDocumentWorkspaceViewInfoElement extends UmbLitElement { }, '_templateUnique', ); - - this.observe( - this.#workspaceContext.variants, - (variants) => { - this._variants = variants; - this.#observeVariants(); - }, - '_variants', - ); } - #observeVariants() { - // Find the oldest variant - const oldestVariant = - this._variants.length > 0 - ? this._variants - .filter((v) => !!v.createDate) - .reduce((prev, current) => (prev.createDate! < current.createDate! ? prev : current)) - : null; - - this._createDate = oldestVariant?.createDate ?? new Date().toISOString(); - } - - #renderVariantStates() { - return repeat( - this._variants, - (variant) => `${variant.culture}_${variant.segment}`, - (variant) => html` -
- ${variant.culture ?? this._invariantCulture} - ${this.#renderStateTag(variant)} -
- `, - ); - } - - #renderStateTag(variant: UmbDocumentVariantModel) { - switch (variant.state) { + #renderStateTag() { + switch (this._variant?.state) { case DocumentVariantStateModel.DRAFT: return html` @@ -174,13 +129,13 @@ export class UmbDocumentWorkspaceViewInfoElement extends UmbLitElement { case DocumentVariantStateModel.PUBLISHED_PENDING_CHANGES: return html` - ${this.localize.term('content_published')} + ${this.localize.term('content_publishedPendingChanges')} `; default: return html` - - ${this.localize.term('content_published')} + + ${this.localize.term('content_notCreated')} `; } @@ -189,65 +144,29 @@ export class UmbDocumentWorkspaceViewInfoElement extends UmbLitElement { override render() { return html`
- - - - + - - +
- ${this.#renderGeneralSection()} + + ${this.#renderGeneralSection()} +
`; } - #renderLinksSection() { - /** TODO Make sure link section is completed */ - if (this._urls && this._urls.length) { - return html` - ${repeat( - this._urls, - (url) => url.culture, - (url) => html` - - ${url.culture} - ${url.url} - - - `, - )} - `; - } else { - return html` - - `; - } - } - #renderGeneralSection() { const editDocumentTypePath = this._routeBuilder?.({ entityType: 'document-type' }) ?? ''; - const editTemplatePath = this._routeBuilder?.({ entityType: 'template' }) ?? ''; return html`
Publication Status - ${this.#renderVariantStates()} -
-
- Created - - - + ${this.#renderStateTag()}
+ ${this.#renderCreateDate()} +
Document Type
+ ${this.#renderTemplateInput()} +
+ Id + ${this._documentUnique} +
+ `; + } + + #renderTemplateInput() { + if (this._allowedTemplates?.length === 0) return nothing; + + const editTemplatePath = this._routeBuilder?.({ entityType: 'template' }) ?? ''; + + return html`
Template ${this._templateUnique @@ -280,9 +213,18 @@ export class UmbDocumentWorkspaceViewInfoElement extends UmbLitElement { @click=${this.#openTemplatePicker}> `}
+ `; + } + + #renderCreateDate() { + if (!this._variant?.createDate) return nothing; + + return html`
- Id - ${this._documentUnique} + Created + + +
`; } @@ -312,7 +254,6 @@ export class UmbDocumentWorkspaceViewInfoElement extends UmbLitElement { } static override styles = [ - UmbTextStyles, css` :host { display: grid; @@ -352,44 +293,6 @@ export class UmbDocumentWorkspaceViewInfoElement extends UmbLitElement { .variant-state > span { color: var(--uui-color-divider-emphasis); } - - // Link section - - #link-section { - display: flex; - flex-direction: column; - text-align: left; - } - - .link-item { - padding: var(--uui-size-space-4) var(--uui-size-space-6); - display: grid; - grid-template-columns: auto 1fr auto; - gap: var(--uui-size-6); - color: inherit; - text-decoration: none; - } - - .link-language { - color: var(--uui-color-divider-emphasis); - } - - .link-content.italic { - font-style: italic; - } - - .link-item uui-icon { - margin-right: var(--uui-size-space-2); - vertical-align: middle; - } - - .link-item.with-href { - cursor: pointer; - } - - .link-item.with-href:hover { - background: var(--uui-color-divider); - } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/extension-insights/collection/extension-collection.element.ts b/src/Umbraco.Web.UI.Client/src/packages/extension-insights/collection/extension-collection.element.ts index 3c22b9e54a..375daeb232 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/extension-insights/collection/extension-collection.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/extension-insights/collection/extension-collection.element.ts @@ -1,17 +1,14 @@ -import type { UmbExtensionCollectionFilterModel } from './types.js'; -import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; -import { html, customElement, css } from '@umbraco-cms/backoffice/external/lit'; +import type { UmbExtensionCollectionFilterModel, UmbExtensionDetailModel } from './types.js'; +import { css, customElement, html } from '@umbraco-cms/backoffice/external/lit'; import { fromCamelCase } from '@umbraco-cms/backoffice/utils'; +import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UMB_COLLECTION_CONTEXT, UmbCollectionDefaultElement } from '@umbraco-cms/backoffice/collection'; import type { UmbDefaultCollectionContext } from '@umbraco-cms/backoffice/collection'; import type { UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; @customElement('umb-extension-collection') export class UmbExtensionCollectionElement extends UmbCollectionDefaultElement { - #collectionContext?: UmbDefaultCollectionContext; - - #inputTimer?: NodeJS.Timeout; - #inputTimerAmount = 500; + #collectionContext?: UmbDefaultCollectionContext; #options: Array