diff --git a/src/Umbraco.Web.UI.Client/devops/tsconfig/index.js b/src/Umbraco.Web.UI.Client/devops/tsconfig/index.js index e05b6550ab..e7dea16f82 100644 --- a/src/Umbraco.Web.UI.Client/devops/tsconfig/index.js +++ b/src/Umbraco.Web.UI.Client/devops/tsconfig/index.js @@ -3,13 +3,23 @@ import { format, resolveConfig } from 'prettier'; import { createImportMap } from '../importmap/index.js'; const tsconfigPath = 'tsconfig.json'; -const tsconfigComment = `// Don't edit this file directly. It is generated by /devops/tsconfig/index.js\n\n`; +const tsconfigComment = ` +/* ------------------------------------------------------------------------- + + +DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js + + + + +--------------------------------------------------------------------------- */ +`; const tsConfigBase = { compilerOptions: { module: 'esnext', - target: 'ES2020', - lib: ['es2020', 'dom', 'dom.iterable'], + target: 'es2022', + lib: ['es2022', 'dom', 'dom.iterable'], outDir: './types', allowSyntheticDefaultImports: true, experimentalDecorators: true, 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 69a04aebbf..b2dcc78cd8 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 @@ -588,7 +588,7 @@ export default { examineManagement: { configuredSearchers: 'Konfigurerede søgere', configuredSearchersDescription: - 'Viser egenskaber og værktøjer til enhver konfigureret søger (dvs. som en\n multi-indekssøger)\n ', + 'Viser egenskaber og værktøjer til enhver konfigureret søger (dvs. som en multi-indekssøger)', fieldValues: 'Feltværdier', healthStatus: 'Sundhedstilstand', healthStatusDescription: 'Indeksets sundhedstilstand, og hvis det kan læses', @@ -597,10 +597,10 @@ export default { indexInfoDescription: 'Viser indeksets egenskaber', manageIndexes: 'Administrer Examine indekserne', manageIndexesDescription: - 'Giver dig mulighed for at se detaljerne for hvert indeks og giver nogle\n værktøjer til styring af indeksørerne\n ', + 'Giver dig mulighed for at se detaljerne for hvert indeks og giver nogle værktøjer til styring af indeksørerne', rebuildIndex: 'Genopbyg indeks', rebuildIndexWarning: - '\n Dette vil medføre, at indekset genopbygges.
\n Afhængigt af hvor meget indhold der er på dit website, kan det tage et stykke tid.
\n Det anbefales ikke at genopbygge et indeks i perioder med høj websitetrafik eller når redaktører redigerer indhold.\n ', + 'Dette vil medføre, at indekset genopbygges.
Afhængigt af hvor meget indhold der er på dit website, kan det tage et stykke tid.
Det anbefales ikke at genopbygge et indeks i perioder med høj websitetrafik eller når redaktører redigerer indhold.', searchers: 'Søgere', searchDescription: 'Søg i indekset og se resultaterne', tools: 'Værktøjer', @@ -608,7 +608,7 @@ export default { fields: 'felter', indexCannotRead: 'Indexet skal bygges igen, for at kunne læses', processIsTakingLonger: - 'Processen tager længere tid end forventet. Kontrollér Umbraco loggen for at se om\n der er sket fejl under operationen\n ', + 'Processen tager længere tid end forventet. Kontrollér Umbraco loggen for at se om der er sket fejl under operationen', indexCannotRebuild: 'Dette index kan ikke genbygges for det ikke har nogen', iIndexPopulator: 'IIndexPopulator', contentInIndex: 'Content in index', 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 28e0912755..7a039d433e 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 @@ -597,7 +597,7 @@ export default { examineManagement: { configuredSearchers: 'Configured Searchers', configuredSearchersDescription: - 'Shows properties and tools for any configured Searcher (i.e. such as a\n multi-index searcher)\n ', + 'Shows properties and tools for any configured Searcher (i.e. such as a multi-index searcher)', fieldValues: 'Field values', healthStatus: 'Health status', healthStatusDescription: 'The health status of the index and if it can be read', @@ -607,10 +607,10 @@ export default { indexInfoDescription: 'Lists the properties of the index', manageIndexes: "Manage Examine's indexes", manageIndexesDescription: - 'Allows you to view the details of each index and provides some tools for\n managing the indexes\n ', + 'Allows you to view the details of each index and provides some tools for managing the indexes', rebuildIndex: 'Rebuild index', rebuildIndexWarning: - '\n This will cause the index to be rebuilt.
\n Depending on how much content there is in your site this could take a while.
\n It is not recommended to rebuild an index during times of high website traffic or when editors are editing content.\n ', + 'This will cause the index to be rebuilt.
Depending on how much content there is in your site this could take a while.
It is not recommended to rebuild an index during times of high website traffic or when editors are editing content.', searchers: 'Searchers', searchDescription: 'Search the index and view the results', tools: 'Tools', @@ -618,7 +618,7 @@ export default { fields: 'fields', indexCannotRead: 'The index cannot be read and will need to be rebuilt', processIsTakingLonger: - 'The process is taking longer than expected, check the Umbraco log to see if there\n have been any errors during this operation\n ', + 'The process is taking longer than expected, check the Umbraco log to see if there have been any errors during this operation', indexCannotRebuild: 'This index cannot be rebuilt because it has no assigned', iIndexPopulator: 'IIndexPopulator', }, diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models.ts index 9b65a819dc..da4e84c5d8 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/models.ts @@ -979,6 +979,11 @@ export enum HealthStatusModel { REBUILDING = 'Rebuilding' } +export type HealthStatusResponseModel = { + status: HealthStatusModel +message?: string | null + }; + export type HelpPageResponseModel = { name?: string | null description?: string | null @@ -993,7 +998,7 @@ parent?: ReferenceByIdModel | null export type IndexResponseModel = { name: string -healthStatus: HealthStatusModel +healthStatus: HealthStatusResponseModel canRebuild: boolean searcherName: string documentCount: number @@ -5236,15 +5241,15 @@ PostWebhook: { GetWebhookById: { id: string + }; +DeleteWebhookById: { + id: string + }; PutWebhookById: { id: string requestBody?: UpdateWebhookRequestModel - }; -DeleteWebhookById: { - id: string - }; GetWebhookEvents: { skip?: number @@ -5259,8 +5264,8 @@ take?: number ,GetWebhook: PagedWebhookResponseModel ,PostWebhook: string ,GetWebhookById: WebhookResponseModel - ,PutWebhookById: string ,DeleteWebhookById: string + ,PutWebhookById: string ,GetWebhookEvents: PagedWebhookEventModel } diff --git a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.ts b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.ts index 503eeca574..84d88c42c7 100644 --- a/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.ts +++ b/src/Umbraco.Web.UI.Client/src/external/backend-api/src/services.ts @@ -9022,20 +9022,17 @@ take * @returns string Success * @throws ApiError */ - public static putWebhookById(data: WebhookData['payloads']['PutWebhookById']): CancelablePromise { + public static deleteWebhookById(data: WebhookData['payloads']['DeleteWebhookById']): CancelablePromise { const { - id, -requestBody + id } = data; return __request(OpenAPI, { - method: 'PUT', + method: 'DELETE', url: '/umbraco/management/api/v1/webhook/{id}', path: { id }, - body: requestBody, - mediaType: 'application/json', responseHeader: 'Umb-Notifications', errors: { 400: `Bad Request`, @@ -9050,17 +9047,20 @@ requestBody * @returns string Success * @throws ApiError */ - public static deleteWebhookById(data: WebhookData['payloads']['DeleteWebhookById']): CancelablePromise { + public static putWebhookById(data: WebhookData['payloads']['PutWebhookById']): CancelablePromise { const { - id + id, +requestBody } = data; return __request(OpenAPI, { - method: 'DELETE', + method: 'PUT', url: '/umbraco/management/api/v1/webhook/{id}', path: { id }, + body: requestBody, + mediaType: 'application/json', responseHeader: 'Umb-Notifications', errors: { 400: `Bad Request`, diff --git a/src/Umbraco.Web.UI.Client/src/mocks/data/examine.data.ts b/src/Umbraco.Web.UI.Client/src/mocks/data/examine.data.ts index 297384f0de..75124653fe 100644 --- a/src/Umbraco.Web.UI.Client/src/mocks/data/examine.data.ts +++ b/src/Umbraco.Web.UI.Client/src/mocks/data/examine.data.ts @@ -20,7 +20,7 @@ export const Indexers: IndexResponseModel[] = [ { name: 'ExternalIndex', canRebuild: true, - healthStatus: HealthStatusModel.HEALTHY, + healthStatus: { status: HealthStatusModel.HEALTHY }, documentCount: 0, fieldCount: 0, searcherName: '', @@ -40,7 +40,7 @@ export const Indexers: IndexResponseModel[] = [ { name: 'InternalIndex', canRebuild: true, - healthStatus: HealthStatusModel.HEALTHY, + healthStatus: { status: HealthStatusModel.HEALTHY }, documentCount: 0, fieldCount: 0, searcherName: '', @@ -60,7 +60,7 @@ export const Indexers: IndexResponseModel[] = [ { name: 'MemberIndex', canRebuild: true, - healthStatus: HealthStatusModel.HEALTHY, + healthStatus: { status: HealthStatusModel.HEALTHY }, fieldCount: 0, documentCount: 0, searcherName: '', diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth-flow.ts b/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth-flow.ts index d47d8ee6aa..4b02ecb73e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth-flow.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/auth/auth-flow.ts @@ -371,8 +371,10 @@ export class UmbAuthFlow { async #performTokenRequest(request: TokenRequest): Promise { try { this.#tokenResponse = await this.#tokenHandler.performTokenRequest(this.#configuration, request); + this.#saveTokenState(); } catch (error) { - // If the token request fails, it means the refresh token is invalid + // If the token request fails, it means the code or refresh token is invalid + this.clearTokenStorage(); console.error('Token request error', error); } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts index 21185774e0..789608dba4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/index.ts @@ -31,4 +31,5 @@ export * from './multiple-text-string-input/index.js'; export * from './popover-layout/index.js'; export * from './ref-item/index.js'; export * from './stack/index.js'; +export * from './split-panel/index.js'; export * from './table/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/split-panel/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/split-panel/index.ts new file mode 100644 index 0000000000..5e03122c68 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/split-panel/index.ts @@ -0,0 +1,3 @@ +import './split-panel.element.js'; + +export * from './split-panel.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/split-panel/split-panel.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/split-panel/split-panel.element.ts new file mode 100644 index 0000000000..58d5fe151c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/split-panel/split-panel.element.ts @@ -0,0 +1,296 @@ +import { + type PropertyValueMap, + LitElement, + css, + customElement, + html, + property, + query, + state, +} from '@umbraco-cms/backoffice/external/lit'; + +/** + * Custom element for a split panel with adjustable divider. + * @element umb-split-panel + * @slot start - Content for the start panel. + * @slot end - Content for the end panel. + * @cssprop --umb-split-panel-initial-position - Initial position of the divider. + * @cssprop --umb-split-panel-start-min-width - Minimum width of the start panel. + * @cssprop --umb-split-panel-end-min-width - Minimum width of the end panel. + * @cssprop --umb-split-panel-divider-touch-area-width - Width of the divider touch area. + * @cssprop --umb-split-panel-divider-width - Width of the divider. + * @cssprop --umb-split-panel-divider-color - Color of the divider. + */ +@customElement('umb-split-panel') +export class UmbSplitPanelElement extends LitElement { + @query('#main') mainElement!: HTMLElement; + @query('#divider-touch-area') dividerTouchAreaElement!: HTMLElement; + @query('#divider') dividerElement!: HTMLElement; + + /** + * Snap points for the divider position. + * Pixel or percent space-separated values: e.g., "100px 50% -75% -200px". + * Negative values are relative to the end of the container. + */ + @property({ type: String }) snap?: string; //TODO: Consider using css variables for snap points. + + /** + * Locking mode for the split panel. + * Possible values: "start", "end", "none" (default). + */ + @property({ type: String }) lock: 'start' | 'end' | 'none' = 'none'; + + /** + * Initial position of the divider. + * Pixel or percent value: e.g., "100px" or "25%". + * Defaults to a CSS variable if not set: "var(--umb-split-panel-initial-position) which defaults to 50%". + */ + @property({ type: String, reflect: true }) position = 'var(--umb-split-panel-initial-position)'; + //TODO: Add support for negative values (relative to end of container) similar to snap points. + + /** Width of the locked panel when in "start" or "end" lock mode */ + #lockedPanelWidth: number = 0; + /** Pixel value for the snap threshold. Determines how close the divider needs to be to a snap point to snap to it. */ + readonly #SNAP_THRESHOLD = 25 as const; + + @state() _hasStartPanel = false; + @state() _hasEndPanel = false; + get #hasBothPanels() { + return this._hasStartPanel && this._hasEndPanel; + } + + #hasInitialized = false; + + disconnectedCallback() { + super.disconnectedCallback(); + this.#disconnect(); + } + + protected updated(_changedProperties: PropertyValueMap | Map): void { + super.updated(_changedProperties); + + if (!this.#hasInitialized) return; + + if (_changedProperties.has('position')) { + if (this.lock !== 'none') { + const { width } = this.mainElement.getBoundingClientRect(); + + let pos = parseFloat(this.position); + + if (this.position.endsWith('%')) { + pos = (pos / 100) * width; + } + + const lockedPanelWidth = this.lock === 'start' ? pos : width - pos; + this.#lockedPanelWidth = lockedPanelWidth; + } + + this.#updateSplit(); + } + } + + #clamp(value: number, min: number, max: number) { + return Math.min(Math.max(value, min), max); + } + + #setPosition(pos: number) { + const { width } = this.mainElement.getBoundingClientRect(); + const localPos = this.#clamp(pos, 0, width); + const percentagePos = (localPos / width) * 100; + this.position = percentagePos + '%'; + } + + #updateSplit() { + // If lock is none + let maxStartWidth = this.position; + let maxEndWidth = '1fr'; + + if (this.lock === 'start') { + maxStartWidth = this.#lockedPanelWidth + 'px'; + maxEndWidth = `1fr`; + } + if (this.lock === 'end') { + maxStartWidth = `1fr`; + maxEndWidth = this.#lockedPanelWidth + 'px'; + } + + this.mainElement.style.gridTemplateColumns = ` + minmax(var(--umb-split-panel-start-min-width), ${maxStartWidth}) + 0px + minmax(var(--umb-split-panel-end-min-width), ${maxEndWidth}) + `; + } + + #onDragStart = (event: PointerEvent | TouchEvent) => { + event.preventDefault(); + + const move = (event: PointerEvent) => { + const { clientX } = event; + const { left, width } = this.mainElement.getBoundingClientRect(); + const localPos = this.#clamp(clientX - left, 0, width); + const mappedPos = mapXAxisToSnap(localPos, width); + + this.#lockedPanelWidth = this.lock === 'start' ? mappedPos : width - mappedPos; + this.#setPosition(mappedPos); + }; + + const stop = () => { + document.removeEventListener('pointermove', move); + document.removeEventListener('pointerup', stop); + + this.dispatchEvent(new CustomEvent('position-changed', { detail: { position: this.position } })); + }; + + const mapXAxisToSnap = (xPos: number, containerWidth: number) => { + const snaps = this.snap?.split(' '); + if (!snaps) return xPos; + + const snapsInPixels = snaps.map((snap) => { + let snapPx = parseFloat(snap); + + if (snap.endsWith('%')) { + snapPx = (snapPx / 100) * containerWidth; + } + + if (snap.startsWith('-')) { + snapPx = containerWidth + snapPx; + } + + return snapPx; + }); + + const closestSnap = snapsInPixels.reduce((prev, curr) => { + return Math.abs(curr - xPos) < Math.abs(prev - xPos) ? curr : prev; + }); + + if (closestSnap < xPos + this.#SNAP_THRESHOLD && closestSnap > xPos - this.#SNAP_THRESHOLD) { + xPos = closestSnap; + } + + return xPos; + }; + + document.addEventListener('pointermove', move, { passive: true }); + document.addEventListener('pointerup', stop); + }; + + #disconnect() { + this.dividerTouchAreaElement.removeEventListener('pointerdown', this.#onDragStart); + this.dividerTouchAreaElement.removeEventListener('touchstart', this.#onDragStart); + this.dividerElement.style.display = 'none'; + this.mainElement.style.display = 'flex'; + this.#hasInitialized = false; + } + + async #connect() { + this.#hasInitialized = true; + + this.mainElement.style.display = 'grid'; + this.mainElement.style.gridTemplateColumns = `${this.position} 0px 1fr`; + this.dividerElement.style.display = 'unset'; + + this.dividerTouchAreaElement.addEventListener('pointerdown', this.#onDragStart); + this.dividerTouchAreaElement.addEventListener('touchstart', this.#onDragStart, { passive: false }); + + // Wait for the next frame to get the correct position of the divider. + await new Promise((resolve) => requestAnimationFrame(resolve)); + + const { left: dividerLeft } = this.shadowRoot!.querySelector('#divider')!.getBoundingClientRect(); + const { left: mainLeft, width: mainWidth } = this.mainElement.getBoundingClientRect(); + const percentagePos = ((dividerLeft - mainLeft) / mainWidth) * 100; + this.position = `${percentagePos}%`; + } + + #onSlotChanged(event: Event) { + const slot = event.target as HTMLSlotElement; + const name = slot.name; + + if (name === 'start') { + this._hasStartPanel = slot.assignedElements().length > 0; + } + if (name === 'end') { + this._hasEndPanel = slot.assignedElements().length > 0; + } + + if (!this.#hasBothPanels) { + if (this.#hasInitialized) { + this.#disconnect(); + } + return; + } + + this.#connect(); + } + + render() { + return html` +
+ +
+
+
+ +
+ `; + } + static styles = css` + :host { + display: contents; + --umb-split-panel-initial-position: 50%; + --umb-split-panel-start-min-width: 0; + --umb-split-panel-end-min-width: 0; + --umb-split-panel-divider-touch-area-width: 20px; + --umb-split-panel-divider-width: 1px; + --umb-split-panel-divider-color: transparent; + --umb-split-panel-slot-overflow: hidden; + } + slot { + overflow: var(--umb-split-panel-slot-overflow); + display: block; + } + #main { + width: 100%; + height: 100%; + display: flex; + position: relative; + z-index: 0; + overflow: hidden; + } + #divider { + height: 100%; + position: relative; + z-index: 999999; + display: none; + } + #divider-touch-area { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: var(--umb-split-panel-divider-touch-area-width); + transform: translateX(-50%); + cursor: col-resize; + } + /* Do we want a line that shows the divider? */ + #divider::after { + content: ''; + position: absolute; + top: 0; + left: 50%; + width: var(--umb-split-panel-divider-width); + height: 100%; + transform: round(translateX(-50%)); + background-color: var(--umb-split-panel-divider-color); + z-index: -1; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-split-panel': UmbSplitPanelElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/split-panel/split-panel.stories.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/split-panel/split-panel.stories.ts new file mode 100644 index 0000000000..3cb9752a4e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/split-panel/split-panel.stories.ts @@ -0,0 +1,50 @@ +import type { Meta, StoryObj } from '@storybook/web-components'; +import './split-panel.element.js'; +import type { UmbSplitPanelElement } from './split-panel.element.js'; +import { html } from '@umbraco-cms/backoffice/external/lit'; + +const meta: Meta = { + title: 'Components/Split Panel', + component: 'umb-split-panel', + argTypes: { + lock: { options: ['none', 'start', 'end'] }, + snap: { control: 'text' }, + position: { control: 'text' }, + }, + args: { + lock: 'start', + snap: '', + position: '50%', + }, +}; + +export default meta; +type Story = StoryObj; + +export const Overview: Story = { + render: (props) => html` + +
Start
+
End
+
+ + `, +}; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/culture/components/input-culture-select/input-culture-select.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/culture/components/input-culture-select/input-culture-select.element.ts index b8bf9fdc87..ea5dfb3295 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/culture/components/input-culture-select/input-culture-select.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/culture/components/input-culture-select/input-culture-select.element.ts @@ -71,7 +71,7 @@ export class UmbInputCultureSelectElement extends UUIFormControlMixin(UmbLitElem } get #fromAvailableCultures() { - return this._cultures.find((culture) => culture.name.toLowerCase() === (this.value as string).toLowerCase()); + return this._cultures.find((culture) => culture.name.toLowerCase() === (this.value as string)?.toLowerCase()); } render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/index.ts index b6c574f8f0..4da1b3c794 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/index.ts @@ -4,7 +4,6 @@ export * from './confirm-modal.token.js'; export * from './debug-modal.token.js'; export * from './embedded-media-modal.token.js'; export * from './entity-user-permission-settings-modal.token.js'; -export * from './examine-fields-settings-modal.token.js'; export * from './icon-picker-modal.token.js'; export * from './item-picker-modal.token.js'; export * from './link-picker-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-default.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-default.element.ts index b30d7ac991..bfb1103f32 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-default.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-default.element.ts @@ -44,6 +44,9 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio UmbExtensionElementInitializer >; + @state() + _splitPanelPosition = '300px'; + constructor() { super(); @@ -54,6 +57,11 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio }); this.#createRoutes(); + + const splitPanelPosition = localStorage.getItem('umb-split-panel-position'); + if (splitPanelPosition) { + this._splitPanelPosition = splitPanelPosition; + } } #createRoutes() { @@ -75,26 +83,37 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio ]; } + #onSplitPanelChange(event: CustomEvent) { + const position = event.detail.position; + localStorage.setItem('umb-split-panel-position', position.toString()); + } + render() { return html` - ${this._sidebarApps && this._sidebarApps.length > 0 - ? html` - - - ${repeat( - this._sidebarApps, - (app) => app.alias, - (app) => app.component, - )} - - ` - : nothing} - - ${this._routes && this._routes.length > 0 - ? html`` + + ${this._sidebarApps && this._sidebarApps.length > 0 + ? html` + + + ${repeat( + this._sidebarApps, + (app) => app.alias, + (app) => app.component, + )} + + ` : nothing} - - + + ${this._routes && this._routes.length > 0 + ? html`` + : nothing} + + + `; } @@ -106,6 +125,18 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio height: 100%; display: flex; } + + umb-split-panel { + --umb-split-panel-start-min-width: 200px; + --umb-split-panel-start-max-width: 400px; + --umb-split-panel-end-min-width: 600px; + --umb-split-panel-slot-overflow: visible; + } + @media only screen and (min-width: 800px) { + umb-split-panel { + --umb-split-panel-initial-position: 300px; + } + } `, ]; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-sidebar-context-menu/section-sidebar-context-menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-sidebar-context-menu/section-sidebar-context-menu.element.ts index c1cdbe7762..6757c34d29 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-sidebar-context-menu/section-sidebar-context-menu.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-sidebar-context-menu/section-sidebar-context-menu.element.ts @@ -136,10 +136,10 @@ export class UmbSectionSidebarContextMenuElement extends UmbLitElement { } #action-modal { position: absolute; - left: var(--umb-section-sidebar-width); height: 100%; z-index: 1; top: 0; + right: calc(var(--umb-section-sidebar-width) * -1); width: var(--umb-section-sidebar-width); border: none; border-left: 1px solid var(--uui-color-border); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-sidebar/section-sidebar.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-sidebar/section-sidebar.element.ts index 65da98a094..5c86779064 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/section/section-sidebar/section-sidebar.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/section/section-sidebar/section-sidebar.element.ts @@ -29,6 +29,7 @@ export class UmbSectionSidebarElement extends UmbLitElement { display: flex; flex-direction: column; z-index: 10; + position: relative; } #scroll-container { diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/manifests.ts index f62071f6c3..dcfcf0f2be 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/entity-actions/create/manifests.ts @@ -14,11 +14,7 @@ const entityActions: Array = [ name: 'Create Document Type Entity Action', weight: 1200, api: UmbCreateDocumentTypeEntityAction, - forEntityTypes: [ - UMB_DOCUMENT_TYPE_ENTITY_TYPE, - UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE, - UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE, - ], + forEntityTypes: [UMB_DOCUMENT_TYPE_ROOT_ENTITY_TYPE, UMB_DOCUMENT_TYPE_FOLDER_ENTITY_TYPE], meta: { icon: 'icon-add', label: '#actions_create', diff --git a/src/Umbraco.Web.UI.Client/src/packages/language/app-language-select/app-language-select.element.ts b/src/Umbraco.Web.UI.Client/src/packages/language/app-language-select/app-language-select.element.ts index a8c3c15d98..0809919909 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/language/app-language-select/app-language-select.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/language/app-language-select/app-language-select.element.ts @@ -115,7 +115,7 @@ export class UmbAppLanguageSelectElement extends UmbLitElement { #toggle { color: var(--uui-color-text); - width: var(--umb-section-sidebar-width); + width: 100%; text-align: left; background: none; border: none; diff --git a/src/Umbraco.Web.UI.Client/src/packages/language/workspace/language/views/language-details-workspace-view.element.ts b/src/Umbraco.Web.UI.Client/src/packages/language/workspace/language/views/language-details-workspace-view.element.ts index 634a3a87e7..68c419061d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/language/workspace/language/views/language-details-workspace-view.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/language/workspace/language/views/language-details-workspace-view.element.ts @@ -112,10 +112,11 @@ export class UmbLanguageDetailsWorkspaceViewElement extends UmbLitElement implem
- + ${this._isNew + ? html` ` + : this._language?.name}
diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/index.ts b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/index.ts new file mode 100644 index 0000000000..28d9f38fba --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/index.ts @@ -0,0 +1 @@ +export * from './modal/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/manifests.ts new file mode 100644 index 0000000000..bf0b12484e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/manifests.ts @@ -0,0 +1,5 @@ +import { manifests as modalManifests } from './modal/manifests.js'; + +import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [...modalManifests]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-settings/examine-fields-settings-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-settings/examine-fields-settings-modal.element.ts new file mode 100644 index 0000000000..24b5634143 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-settings/examine-fields-settings-modal.element.ts @@ -0,0 +1,76 @@ +import type { + UmbExamineFieldsSettingsModalData, + UmbExamineFieldsSettingsModalValue, + UmbExamineFieldSettingsType, +} from './examine-fields-settings-modal.token.js'; +import { html, css, customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; + +@customElement('umb-examine-fields-settings-modal') +export class UmbExamineFieldsSettingsModalElement extends UmbModalBaseElement< + UmbExamineFieldsSettingsModalData, + UmbExamineFieldsSettingsModalValue +> { + render() { + return html` + ${this.#renderFields()} +
+ +
+
`; + } + + #setExposed(fieldSetting: UmbExamineFieldSettingsType) { + const newField: UmbExamineFieldSettingsType = { ...fieldSetting, exposed: !fieldSetting.exposed }; + + const updatedFields = + this.modalContext?.getValue().fields.map((field) => { + if (field.name === fieldSetting.name) return newField; + else return field; + }) ?? []; + + this.modalContext?.updateValue({ fields: updatedFields }); + } + + #renderFields() { + if (!this.value.fields.length) return; + return html` + ${Object.values(this.value.fields).map((field) => { + return html` +
`; + })} +
`; + } + + static styles = [ + UmbTextStyles, + css` + :host { + display: relative; + } + + uui-scroll-container { + overflow-y: scroll; + max-height: 100%; + min-height: 0; + flex: 1; + } + `, + ]; +} + +export default UmbExamineFieldsSettingsModalElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-examine-fields-settings-modal': UmbExamineFieldsSettingsModalElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/examine-fields-settings-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-settings/examine-fields-settings-modal.token.ts similarity index 64% rename from src/Umbraco.Web.UI.Client/src/packages/core/modal/token/examine-fields-settings-modal.token.ts rename to src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-settings/examine-fields-settings-modal.token.ts index ccf52d6a9d..43b1fab830 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/token/examine-fields-settings-modal.token.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-settings/examine-fields-settings-modal.token.ts @@ -1,20 +1,20 @@ -import { UmbModalToken } from './modal-token.js'; +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; export type UmbExamineFieldsSettingsModalData = never; -type FieldSettingsType = { +export type UmbExamineFieldSettingsType = { name: string; exposed: boolean; }; export type UmbExamineFieldsSettingsModalValue = { - fields: Array; + fields: Array; }; export const UMB_EXAMINE_FIELDS_SETTINGS_MODAL = new UmbModalToken< UmbExamineFieldsSettingsModalData, UmbExamineFieldsSettingsModalValue ->('Umb.Modal.ExamineFieldsSettings', { +>('Umb.Modal.Examine.FieldsSettings', { modal: { type: 'sidebar', size: 'small', diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-settings/index.ts b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-settings/index.ts new file mode 100644 index 0000000000..6ca0fb4eb2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-settings/index.ts @@ -0,0 +1,2 @@ +export * from './examine-fields-settings-modal.element.js'; +export * from './examine-fields-settings-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/modal-views/fields-viewer.element.ts b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-viewer/examine-fields-viewer-modal.element.ts similarity index 60% rename from src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/modal-views/fields-viewer.element.ts rename to src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-viewer/examine-fields-viewer-modal.element.ts index 66fa6a5d0f..af597bf466 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/modal-views/fields-viewer.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-viewer/examine-fields-viewer-modal.element.ts @@ -1,11 +1,15 @@ +import type { + UmbExamineFieldsViewerModalData, + UmbExamineFieldsViewerModalValue, +} from './examine-fields-viewer-modal.token.js'; import { html, css, nothing, customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; -import type { SearchResultResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; -@customElement('umb-modal-element-fields-viewer') -export class UmbModalElementFieldsViewerElement extends UmbModalBaseElement< - SearchResultResponseModel & { name: string } +@customElement('umb-examine-fields-viewer-modal') +export class UmbExamineFieldsViewerModalElement extends UmbModalBaseElement< + UmbExamineFieldsViewerModalData, + UmbExamineFieldsViewerModalValue > { private _handleClose() { this.modalContext?.reject(); @@ -15,7 +19,7 @@ export class UmbModalElementFieldsViewerElement extends UmbModalBaseElement< if (!this.data) return nothing; return html` - + @@ -23,7 +27,7 @@ export class UmbModalElementFieldsViewerElement extends UmbModalBaseElement< Field Value - ${Object.values(this.data.fields ?? []).map((cell) => { + ${Object.values(this.data.searchResult.fields ?? []).map((cell) => { return html` ${cell.name} ${cell.values?.join(', ')} @@ -32,10 +36,13 @@ export class UmbModalElementFieldsViewerElement extends UmbModalBaseElement< -
- Close +
+
- + `; } @@ -45,11 +52,6 @@ export class UmbModalElementFieldsViewerElement extends UmbModalBaseElement< :host { display: relative; } - uui-dialog-layout { - display: flex; - flex-direction: column; - height: 100%; - } span { display: block; @@ -57,22 +59,18 @@ export class UmbModalElementFieldsViewerElement extends UmbModalBaseElement< } uui-scroll-container { - line-height: 0; overflow-y: scroll; max-height: 100%; min-height: 0; } - div { - margin-top: var(--uui-size-space-5); - display: flex; - flex-direction: row-reverse; - } `, ]; } +export default UmbExamineFieldsViewerModalElement; + declare global { interface HTMLElementTagNameMap { - 'umb-modal-element-fields-viewer': UmbModalElementFieldsViewerElement; + 'umb-examine-fields-viewer-modal': UmbExamineFieldsViewerModalElement; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-viewer/examine-fields-viewer-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-viewer/examine-fields-viewer-modal.token.ts new file mode 100644 index 0000000000..c0bea59bfe --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-viewer/examine-fields-viewer-modal.token.ts @@ -0,0 +1,19 @@ +import type { SearchResultResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; + +export type UmbExamineFieldsViewerModalData = { + name: string; + searchResult: SearchResultResponseModel; +}; + +export type UmbExamineFieldsViewerModalValue = never; + +export const UMB_EXAMINE_FIELDS_VIEWER_MODAL = new UmbModalToken< + UmbExamineFieldsViewerModalData, + UmbExamineFieldsViewerModalValue +>('Umb.Modal.Examine.FieldsViewer', { + modal: { + type: 'sidebar', + size: 'small', + }, +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-viewer/index.ts b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-viewer/index.ts new file mode 100644 index 0000000000..de13abcf42 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/fields-viewer/index.ts @@ -0,0 +1,2 @@ +export * from './examine-fields-viewer-modal.element.js'; +export * from './examine-fields-viewer-modal.token.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/index.ts b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/index.ts new file mode 100644 index 0000000000..6ade5af6a6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/index.ts @@ -0,0 +1,2 @@ +export * from './fields-settings/index.js'; +export * from './fields-viewer/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/manifests.ts new file mode 100644 index 0000000000..42a6b8aa5c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/modal/manifests.ts @@ -0,0 +1,18 @@ +import type { ManifestModal, ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; + +const modals: Array = [ + { + type: 'modal', + alias: 'Umb.Modal.Examine.FieldsSettings', + name: 'Examine Field Settings Modal', + js: () => import('./fields-settings/examine-fields-settings-modal.element.js'), + }, + { + type: 'modal', + alias: 'Umb.Modal.Examine.FieldsViewer', + name: 'Examine Field Viewer Modal', + js: () => import('./fields-viewer/examine-fields-viewer-modal.element.js'), + }, +]; + +export const manifests: Array = [...modals]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/modal-views/fields-settings-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/modal-views/fields-settings-modal.element.ts deleted file mode 100644 index 0e9570875d..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/modal-views/fields-settings-modal.element.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { html, css, customElement } from '@umbraco-cms/backoffice/external/lit'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import type { - UmbExamineFieldsSettingsModalValue, - UmbExamineFieldsSettingsModalData} from '@umbraco-cms/backoffice/modal'; -import { - UmbModalBaseElement, -} from '@umbraco-cms/backoffice/modal'; - -@customElement('umb-examine-fields-settings-modal') -export default class UmbExamineFieldsSettingsModalElement extends UmbModalBaseElement< - UmbExamineFieldsSettingsModalData, - UmbExamineFieldsSettingsModalValue -> { - render() { - if (this.value.fields) { - return html` - - - - ${Object.values(this.value.fields).map((field, index) => { - return html` -
`; - })} -
-
-
- Close -
-
- `; - } else { - return ''; - } - } - - static styles = [ - UmbTextStyles, - css` - :host { - display: relative; - } - - uui-dialog-layout { - display: flex; - flex-direction: column; - height: 100%; - background-color: var(--uui-color-surface); - box-shadow: var(--uui-shadow-depth-1, 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24)); - border-radius: var(--uui-border-radius); - padding: var(--uui-size-space-5); - box-sizing: border-box; - } - - uui-scroll-container { - overflow-y: scroll; - max-height: 100%; - min-height: 0; - flex: 1; - } - - div { - margin-top: var(--uui-size-space-5); - display: flex; - flex-direction: row-reverse; - } - `, - ]; -} - -declare global { - interface HTMLElementTagNameMap { - 'umb-examine-fields-settings-modal': UmbExamineFieldsSettingsModalElement; - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-indexers.ts b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-indexers.ts index 9d86f91654..d9bb5f3336 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-indexers.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-indexers.ts @@ -1,7 +1,7 @@ import type { UUIButtonState } from '@umbraco-cms/backoffice/external/uui'; import { css, html, nothing, customElement, property, state } from '@umbraco-cms/backoffice/external/lit'; import { umbConfirmModal } from '@umbraco-cms/backoffice/modal'; -import type { IndexResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; +import type { HealthStatusResponseModel, IndexResponseModel } from '@umbraco-cms/backoffice/external/backend-api'; import { HealthStatusModel, IndexerService } from '@umbraco-cms/backoffice/external/backend-api'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; @@ -25,35 +25,51 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement { connectedCallback() { super.connectedCallback(); - this._getIndexData(); + this.#loadData(); } - private async _getIndexData() { + async #loadData() { + this._indexData = await this.#getIndexData(); + + if (this._indexData?.healthStatus.status === HealthStatusModel.REBUILDING) { + this._buttonState = 'waiting'; + this._continuousPolling(); + } else { + this._loading = false; + } + } + + async #getIndexData() { const { data } = await tryExecuteAndNotify( this, IndexerService.getIndexerByIndexName({ indexName: this.indexName }), ); - this._indexData = data; + return data; + } - // TODO: Add continuous polling to update the status - if (this._indexData?.healthStatus === HealthStatusModel.REBUILDING) { - this._buttonState = 'waiting'; + private async _continuousPolling() { + //Checking the server every 5 seconds to see if the index is still rebuilding. + while (this._buttonState === 'waiting') { + await new Promise((resolve) => setTimeout(resolve, 5000)); + this._indexData = await this.#getIndexData(); + if (this._indexData?.healthStatus.status !== HealthStatusModel.REBUILDING) { + this._buttonState = 'success'; + } } - - this._loading = false; + return; } private async _onRebuildHandler() { await umbConfirmModal(this, { - headline: `Rebuild ${this.indexName}`, - content: html` - This will cause the index to be rebuilt.
+ headline: `${this.localize.term('examineManagement_rebuildIndex')} ${this.indexName}`, + content: html`This will cause the index to be rebuilt.
Depending on how much content there is in your site this could take a while.
It is not recommended to rebuild an index during times of high website traffic or when editors are editing - content. - `, + content.
`, color: 'danger', - confirmLabel: 'Rebuild', + confirmLabel: this.localize.term('examineManagement_rebuildIndex'), }); this._rebuild(); @@ -68,9 +84,22 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement { this._buttonState = 'failed'; return; } - this._buttonState = 'success'; - await this._getIndexData(); + await this.#loadData(); + } + + #renderHealthStatus(healthStatus: HealthStatusResponseModel) { + const msg = healthStatus.message ? healthStatus.message : healthStatus.status; + switch (healthStatus.status) { + case HealthStatusModel.HEALTHY: + return html`${msg}`; + case HealthStatusModel.UNHEALTHY: + return html`${msg}`; + case HealthStatusModel.REBUILDING: + return html`${msg}`; + default: + return; + } } render() { @@ -79,43 +108,41 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement { return html`

- Health Status
- The health status of the ${this.indexName} and if it can be read + Health Status
+ The health status of the ${this.indexName} and if it can be read

-
- - ${ - this._indexData.healthStatus === HealthStatusModel.UNHEALTHY - ? html`` - : html`` - } - - - ${this._indexData.healthStatus} -
+
${this.#renderHealthStatus(this._indexData.healthStatus)}
${this.renderIndexSearch()} ${this.renderPropertyList()} ${this.renderTools()} `; } private renderIndexSearch() { - if (!this._indexData || this._indexData.healthStatus !== HealthStatusModel.HEALTHY) return nothing; + // Do we want to show the search while rebuilding? + if (!this._indexData || this._indexData.healthStatus.status === HealthStatusModel.REBUILDING) return nothing; return html``; } private renderPropertyList() { if (!this._indexData) return nothing; - return html` -

Lists the properties of the ${this.indexName}

+ return html` +

+ Lists the properties of the ${this.indexName} +

- documentCount - ${this._indexData.documentCount} + DocumentCount + ${this._indexData.documentCount} - fieldCount - ${this._indexData.fieldCount} + FieldCount + ${this._indexData.fieldCount} ${this._indexData.providerProperties ? Object.entries(this._indexData.providerProperties).map((entry) => { @@ -130,23 +157,26 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement { } private renderTools() { - return html` -

Tools to manage the ${this.indexName}

+ return html` +

Tools to manage the ${this.indexName}

- Rebuild - + label=${this.localize.term('examineManagement_rebuildIndex')}>
`; } static styles = [ UmbTextStyles, css` + #health-status { + display: flex; + gap: var(--uui-size-6); + } + :host { display: block; } @@ -190,13 +220,6 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement { padding-right: var(--uui-size-space-5); } - .positive { - color: var(--uui-color-positive); - } - .danger { - color: var(--uui-color-danger); - } - button { background: none; border: none; diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-overview.ts b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-overview.ts index c14200b221..0adc0f2b11 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-overview.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-overview.ts @@ -40,19 +40,38 @@ export class UmbDashboardExamineOverviewElement extends UmbLitElement { this._loadingSearchers = false; } + #renderStatus(status: HealthStatusModel) { + switch (status) { + case HealthStatusModel.HEALTHY: + return html``; + case HealthStatusModel.UNHEALTHY: + return html``; + case HealthStatusModel.REBUILDING: + return html``; + default: + return; + } + } + render() { return html` - +

- Manage Examine's indexes
- Allows you to view the details of each index and provides some tools for managing the indexes + Manage Examine's indexes
+ Allows you to view the details of each index and provides some tools for managing the indexes

${this.renderIndexersList()}
- +

- Configured Searchers
- Shows properties and tools for any configured Searcher (i.e. such as a multi-index searcher) + Configured Searchers
+ Shows properties and tools for any configured Searcher (i.e. such as a multi-index searcher)

${this.renderSearchersList()}
@@ -66,16 +85,7 @@ export class UmbDashboardExamineOverviewElement extends UmbLitElement { ${this._indexers.map((index) => { return html` - - - ${ - index.healthStatus === HealthStatusModel.UNHEALTHY - ? html`` - : html`` - } - - - + ${this.#renderStatus(index.healthStatus.status)} ${index.name} diff --git a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-searchers.ts b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-searchers.ts index a37110ec08..ab604b2ea6 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-searchers.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/search/examine-management-dashboard/views/section-view-examine-searchers.ts @@ -1,14 +1,16 @@ +import { UMB_EXAMINE_FIELDS_SETTINGS_MODAL, UMB_EXAMINE_FIELDS_VIEWER_MODAL } from '../modal/index.js'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; import { css, html, nothing, customElement, state, query, property } from '@umbraco-cms/backoffice/external/lit'; -import { UMB_MODAL_MANAGER_CONTEXT, UMB_EXAMINE_FIELDS_SETTINGS_MODAL } from '@umbraco-cms/backoffice/modal'; +import { + UMB_MODAL_MANAGER_CONTEXT, + UMB_WORKSPACE_MODAL, + UmbModalRouteRegistrationController, +} from '@umbraco-cms/backoffice/modal'; import type { SearchResultResponseModel, FieldPresentationModel } from '@umbraco-cms/backoffice/external/backend-api'; import { SearcherService } from '@umbraco-cms/backoffice/external/backend-api'; import { UmbLitElement, umbFocus } from '@umbraco-cms/backoffice/lit-element'; import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; -import './modal-views/fields-viewer.element.js'; -import './modal-views/fields-settings-modal.element.js'; - interface ExposedSearchResultField { name: string; exposed: boolean; @@ -31,15 +33,27 @@ export class UmbDashboardExamineSearcherElement extends UmbLitElement { @query('#search-input') private _searchInput!: HTMLInputElement; - private _onNameClick() { - // TODO: - alert('TODO: Open workspace for ' + this.searcherName); - } + @state() + private _workspacePath = 'aa'; private _onKeyPress(e: KeyboardEvent) { e.key == 'Enter' ? this._onSearch() : undefined; } + #entityType = ''; + + constructor() { + super(); + new UmbModalRouteRegistrationController(this, UMB_WORKSPACE_MODAL) + .addAdditionalPath(':entityType') + .onSetup((routingInfo) => { + return { data: { entityType: routingInfo.entityType, preset: {} } }; + }) + .observeRouteBuilder((routeBuilder) => { + this._workspacePath = routeBuilder({ entityType: this.#entityType }); + }); + } + private async _onSearch() { if (!this._searchInput.value.length) return; this._searchLoading = true; @@ -86,36 +100,48 @@ export class UmbDashboardExamineSearcherElement extends UmbLitElement { const modalContext = modalManager.open(this, UMB_EXAMINE_FIELDS_SETTINGS_MODAL, { value: { fields: this._exposedFields ?? [] }, }); - modalContext?.onSubmit().then((value) => { - this._exposedFields = value.fields; - }); + await modalContext.onSubmit().catch(() => undefined); + + const value = modalContext.getValue(); + + this._exposedFields = value?.fields; } async #onFieldViewClick(rowData: SearchResultResponseModel) { const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT); - modalManager.open(this, 'umb-modal-element-fields-viewer', { + + const modalContext = modalManager.open(this, UMB_EXAMINE_FIELDS_VIEWER_MODAL, { modal: { type: 'sidebar', size: 'medium', }, - data: { ...rowData, name: this.getSearchResultNodeName(rowData) }, + data: { searchResult: rowData, name: this.getSearchResultNodeName(rowData) }, }); + await modalContext.onSubmit().catch(() => undefined); } render() { return html` - -

Search the ${this.searcherName} and view the results

+ +

+ Search the ${this.searcherName} and view the results +

- Search +
${this.renderSearchResults()}
@@ -128,28 +154,44 @@ export class UmbDashboardExamineSearcherElement extends UmbLitElement { return nodeNameField?.values?.join(', ') ?? ''; } + #getEntityTypeFromIndexType(indexType: string) { + switch (indexType) { + case 'content': + return 'document'; + default: + return indexType; + } + } + private renderSearchResults() { if (this._searchLoading) return html``; if (!this._searchResults) return nothing; if (!this._searchResults.length) { - return html`

No results found

`; + return html`

${this.localize.term('examineManagement_noResults')}

`; } return html`
Score - Id - Name - Fields + ${this.localize.term('general_id')} + ${this.localize.term('general_name')} + ${this.localize.term('examineManagement_fields')} ${this.renderHeadCells()} ${this._searchResults?.map((rowData) => { + const indexType = rowData.fields?.find((field) => field.name === '__IndexType')?.values?.join(', ') ?? ''; + this.#entityType = this.#getEntityTypeFromIndexType(indexType); + const unique = rowData.fields?.find((field) => field.name === '__Key')?.values?.join(', ') ?? ''; + return html` ${rowData.score} ${rowData.id} - + ${this.getSearchResultNodeName(rowData)} @@ -157,9 +199,10 @@ export class UmbDashboardExamineSearcherElement extends UmbLitElement { this.#onFieldViewClick(rowData)}> - ${rowData.fields ? Object.keys(rowData.fields).length : ''} fields + ${rowData.fields ? Object.keys(rowData.fields).length : ''} + ${this.localize.term('examineManagement_fields')} ${rowData.fields ? this.renderBodyCells(rowData.fields) : ''} @@ -185,7 +228,7 @@ export class UmbDashboardExamineSearcherElement extends UmbLitElement { ${field.name} + this.#removeEvent(item.alias)}> `, )} `; @@ -60,7 +60,7 @@ export class UmbInputWebhookEventsElement extends UmbLitElement { render() { return html`${this.#renderEvents()} - `; + `; } static styles = [ @@ -73,7 +73,7 @@ export class UmbInputWebhookEventsElement extends UmbLitElement { align-items: center; } - #add { + #choose { grid-column: -1 / 1; } `, diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index 63d9866574..316fbbe6d5 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -1,5 +1,12 @@ -// Don't edit this file directly. It is generated by /devops/tsconfig/index.js +/* ------------------------------------------------------------------------- + +DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js + + + + +--------------------------------------------------------------------------- */ { "compilerOptions": { "module": "esnext",