Merge remote-tracking branch 'origin/main' into feature/dropzone-management

This commit is contained in:
Lone Iversen
2024-05-10 10:26:13 +02:00
36 changed files with 785 additions and 267 deletions

View File

@@ -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,

View File

@@ -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.<br />\n Afhængigt af hvor meget indhold der er på dit website, kan det tage et stykke tid.<br />\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.<br /> Afhængigt af hvor meget indhold der er på dit website, kan det tage et stykke tid.<br /> 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',

View File

@@ -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.<br />\n Depending on how much content there is in your site this could take a while.<br />\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.<br /> Depending on how much content there is in your site this could take a while.<br /> 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',
},

View File

@@ -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
}

View File

@@ -9022,20 +9022,17 @@ take
* @returns string Success
* @throws ApiError
*/
public static putWebhookById(data: WebhookData['payloads']['PutWebhookById']): CancelablePromise<WebhookData['responses']['PutWebhookById']> {
public static deleteWebhookById(data: WebhookData['payloads']['DeleteWebhookById']): CancelablePromise<WebhookData['responses']['DeleteWebhookById']> {
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<WebhookData['responses']['DeleteWebhookById']> {
public static putWebhookById(data: WebhookData['payloads']['PutWebhookById']): CancelablePromise<WebhookData['responses']['PutWebhookById']> {
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`,

View File

@@ -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: '',

View File

@@ -371,8 +371,10 @@ export class UmbAuthFlow {
async #performTokenRequest(request: TokenRequest): Promise<void> {
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);
}
}

View File

@@ -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';

View File

@@ -0,0 +1,3 @@
import './split-panel.element.js';
export * from './split-panel.element.js';

View File

@@ -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<any> | Map<PropertyKey, unknown>): 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`
<div id="main">
<slot
name="start"
@slotchange=${this.#onSlotChanged}
style="width: ${this._hasStartPanel ? '100%' : '0'}"></slot>
<div id="divider">
<div id="divider-touch-area" tabindex="0"></div>
</div>
<slot name="end" @slotchange=${this.#onSlotChanged} style="width: ${this._hasEndPanel ? '100%' : '0'}"></slot>
</div>
`;
}
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;
}
}

View File

@@ -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<UmbSplitPanelElement> = {
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<UmbSplitPanelElement>;
export const Overview: Story = {
render: (props) => html`
<umb-split-panel .lock=${props.lock} .snap=${props.snap} .position=${props.position}>
<div id="start" slot="start">Start</div>
<div id="end" slot="end">End</div>
</umb-split-panel>
<style>
#start,
#end {
background-color: #ffffff;
color: #383838;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
font-weight: 600;
}
#start {
border-right: 2px solid #e5e5e5;
min-height: 300px;
}
#end {
border-left: 2px solid #e5e5e5;
}
</style>
`,
};

View File

@@ -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() {

View File

@@ -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';

View File

@@ -44,6 +44,9 @@ export class UmbSectionDefaultElement extends UmbLitElement implements UmbSectio
UmbExtensionElementInitializer<ManifestSectionSidebarApp | ManifestSectionSidebarAppMenuKind>
>;
@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`
<!-- TODO: these extensions should be combined into one type: sectionSidebarApp with a "subtype" -->
<umb-section-sidebar>
${repeat(
this._sidebarApps,
(app) => app.alias,
(app) => app.component,
)}
</umb-section-sidebar>
`
: nothing}
<umb-section-main>
${this._routes && this._routes.length > 0
? html`<umb-router-slot id="router-slot" .routes=${this._routes}></umb-router-slot>`
<umb-split-panel
lock="start"
snap="300px"
@position-changed=${this.#onSplitPanelChange}
.position=${this._splitPanelPosition}>
${this._sidebarApps && this._sidebarApps.length > 0
? html`
<!-- TODO: these extensions should be combined into one type: sectionSidebarApp with a "subtype" -->
<umb-section-sidebar slot="start">
${repeat(
this._sidebarApps,
(app) => app.alias,
(app) => app.component,
)}
</umb-section-sidebar>
`
: nothing}
<slot></slot>
</umb-section-main>
<umb-section-main slot="end">
${this._routes && this._routes.length > 0
? html`<umb-router-slot id="router-slot" .routes=${this._routes}></umb-router-slot>`
: nothing}
<slot></slot>
</umb-section-main>
</umb-split-panel>
`;
}
@@ -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;
}
}
`,
];
}

View File

@@ -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);

View File

@@ -29,6 +29,7 @@ export class UmbSectionSidebarElement extends UmbLitElement {
display: flex;
flex-direction: column;
z-index: 10;
position: relative;
}
#scroll-container {

View File

@@ -14,11 +14,7 @@ const entityActions: Array<ManifestTypes> = [
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',

View File

@@ -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;

View File

@@ -112,10 +112,11 @@ export class UmbLanguageDetailsWorkspaceViewElement extends UmbLitElement implem
<umb-property-layout label="Language">
<div slot="editor">
<!-- TODO: disable already created cultures in the select -->
<umb-input-culture-select
value=${ifDefined(this._language?.unique)}
@change=${this.#handleCultureChange}
?readonly=${this._isNew === false}></umb-input-culture-select>
${this._isNew
? html` <umb-input-culture-select
value=${ifDefined(this._language?.unique)}
@change=${this.#handleCultureChange}></umb-input-culture-select>`
: this._language?.name}
</div>
</umb-property-layout>

View File

@@ -0,0 +1 @@
export * from './modal/index.js';

View File

@@ -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<ManifestTypes> = [...modalManifests];

View File

@@ -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`<umb-body-layout headline=${this.localize.term('examineManagement_fields')}>
<uui-scroll-container id="field-settings"> ${this.#renderFields()} </uui-scroll-container>
<div slot="actions">
<uui-button
look="primary"
label=${this.localize.term('general_close')}
@click="${this._submitModal}"></uui-button>
</div>
</umb-body-layout>`;
}
#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`<span>
${Object.values(this.value.fields).map((field) => {
return html`<uui-toggle
name="${field.name}"
label="${field.name}"
.checked="${field.exposed}"
@change="${() => this.#setExposed(field)}"></uui-toggle>
<br />`;
})}
</span>`;
}
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;
}
}

View File

@@ -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<FieldSettingsType>;
fields: Array<UmbExamineFieldSettingsType>;
};
export const UMB_EXAMINE_FIELDS_SETTINGS_MODAL = new UmbModalToken<
UmbExamineFieldsSettingsModalData,
UmbExamineFieldsSettingsModalValue
>('Umb.Modal.ExamineFieldsSettings', {
>('Umb.Modal.Examine.FieldsSettings', {
modal: {
type: 'sidebar',
size: 'small',

View File

@@ -0,0 +1,2 @@
export * from './examine-fields-settings-modal.element.js';
export * from './examine-fields-settings-modal.token.js';

View File

@@ -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`
<uui-dialog-layout class="uui-text" headline="${this.data.name}">
<umb-body-layout headline="${this.data?.name}">
<uui-scroll-container id="field-viewer">
<span>
<uui-table>
@@ -23,7 +27,7 @@ export class UmbModalElementFieldsViewerElement extends UmbModalBaseElement<
<uui-table-head-cell> Field </uui-table-head-cell>
<uui-table-head-cell> Value </uui-table-head-cell>
</uui-table-head>
${Object.values(this.data.fields ?? []).map((cell) => {
${Object.values(this.data.searchResult.fields ?? []).map((cell) => {
return html`<uui-table-row>
<uui-table-cell> ${cell.name} </uui-table-cell>
<uui-table-cell> ${cell.values?.join(', ')} </uui-table-cell>
@@ -32,10 +36,13 @@ export class UmbModalElementFieldsViewerElement extends UmbModalBaseElement<
</uui-table>
</span>
</uui-scroll-container>
<div>
<uui-button look="primary" @click="${this._handleClose}">Close</uui-button>
<div slot="actions">
<uui-button
look="primary"
label=${this.localize.term('general_close')}
@click=${this._rejectModal}></uui-button>
</div>
</uui-dialog-layout>
</umb-body-layout>
`;
}
@@ -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;
}
}

View File

@@ -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',
},
});

View File

@@ -0,0 +1,2 @@
export * from './examine-fields-viewer-modal.element.js';
export * from './examine-fields-viewer-modal.token.js';

View File

@@ -0,0 +1,2 @@
export * from './fields-settings/index.js';
export * from './fields-viewer/index.js';

View File

@@ -0,0 +1,18 @@
import type { ManifestModal, ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
const modals: Array<ManifestModal> = [
{
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<ManifestTypes> = [...modals];

View File

@@ -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`
<uui-dialog-layout headline="Show fields">
<uui-scroll-container id="field-settings">
<span>
${Object.values(this.value.fields).map((field, index) => {
return html`<uui-toggle
name="${field.name}"
label="${field.name}"
.checked="${field.exposed}"
@change="${() => {
this.value.fields ? (this.value.fields[index].exposed = !field.exposed) : '';
}}"></uui-toggle>
<br />`;
})}
</span>
</uui-scroll-container>
<div>
<uui-button look="primary" label="Close sidebar" @click="${this._submitModal}">Close</uui-button>
</div>
</uui-dialog-layout>
`;
} 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;
}
}

View File

@@ -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.<br />
headline: `${this.localize.term('examineManagement_rebuildIndex')} ${this.indexName}`,
content: html`<umb-localize key="examineManagement_rebuildIndexWarning"
>This will cause the index to be rebuilt.<br />
Depending on how much content there is in your site this could take a while.<br />
It is not recommended to rebuild an index during times of high website traffic or when editors are editing
content.
`,
content.</umb-localize
> `,
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`<umb-icon name="icon-check color-green"></umb-icon>${msg}`;
case HealthStatusModel.UNHEALTHY:
return html`<umb-icon name="icon-error color-red"></umb-icon>${msg}`;
case HealthStatusModel.REBUILDING:
return html`<umb-icon name="icon-time color-yellow"></umb-icon>${msg}`;
default:
return;
}
}
render() {
@@ -79,43 +108,41 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement {
return html`
<uui-box headline="${this.indexName}">
<p>
<strong>Health Status</strong><br />
The health status of the ${this.indexName} and if it can be read
<strong><umb-localize key="examineManagement_healthStatus">Health Status</umb-localize></strong
><br />
<umb-localize key="examineManagement_healthStatusDescription"
>The health status of the ${this.indexName} and if it can be read</umb-localize
>
</p>
<div>
<uui-icon-essentials>
${
this._indexData.healthStatus === HealthStatusModel.UNHEALTHY
? html`<uui-icon name="wrong" class="danger"></uui-icon>`
: html`<uui-icon name="check" class="positive"></uui-icon>`
}
</uui-icon>
</uui-icon-essentials>
${this._indexData.healthStatus}
</div>
<div id="health-status">${this.#renderHealthStatus(this._indexData.healthStatus)}</div>
</uui-box>
${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`<umb-dashboard-examine-searcher .searcherName="${this.indexName}"></umb-dashboard-examine-searcher>`;
}
private renderPropertyList() {
if (!this._indexData) return nothing;
return html`<uui-box headline="Index info">
<p>Lists the properties of the ${this.indexName}</p>
return html`<uui-box headline=${this.localize.term('examineManagement_indexInfo')}>
<p>
<umb-localize key="examineManagement_indexInfoDescription"
>Lists the properties of the ${this.indexName}</umb-localize
>
</p>
<uui-table class="info">
<uui-table-row>
<uui-table-cell style="width:0px; font-weight: bold;"> documentCount </uui-table-cell>
<uui-table-cell>${this._indexData.documentCount} </uui-table-cell>
<uui-table-cell style="width:0px; font-weight: bold;">DocumentCount</uui-table-cell>
<uui-table-cell>${this._indexData.documentCount}</uui-table-cell>
</uui-table-row>
<uui-table-row>
<uui-table-cell style="width:0px; font-weight: bold;"> fieldCount </uui-table-cell>
<uui-table-cell>${this._indexData.fieldCount} </uui-table-cell>
<uui-table-cell style="width:0px; font-weight: bold;">FieldCount</uui-table-cell>
<uui-table-cell>${this._indexData.fieldCount}</uui-table-cell>
</uui-table-row>
${this._indexData.providerProperties
? Object.entries(this._indexData.providerProperties).map((entry) => {
@@ -130,23 +157,26 @@ export class UmbDashboardExamineIndexElement extends UmbLitElement {
}
private renderTools() {
return html` <uui-box headline="Tools">
<p>Tools to manage the ${this.indexName}</p>
return html` <uui-box headline=${this.localize.term('examineManagement_tools')}>
<p><umb-localize key="examineManagement_toolsDescription">Tools to manage the ${this.indexName}</umb-localize></p>
<uui-button
color="danger"
look="primary"
.state="${this._buttonState}"
@click="${this._onRebuildHandler}"
.disabled="${this._indexData?.canRebuild ? false : true}"
label="Rebuild index">
Rebuild
</uui-button>
label=${this.localize.term('examineManagement_rebuildIndex')}></uui-button>
</uui-box>`;
}
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;

View File

@@ -40,19 +40,38 @@ export class UmbDashboardExamineOverviewElement extends UmbLitElement {
this._loadingSearchers = false;
}
#renderStatus(status: HealthStatusModel) {
switch (status) {
case HealthStatusModel.HEALTHY:
return html`<umb-icon name="icon-check color-green"></umb-icon>`;
case HealthStatusModel.UNHEALTHY:
return html`<umb-icon name="icon-error color-red"></umb-icon>`;
case HealthStatusModel.REBUILDING:
return html`<umb-icon name="icon-time color-yellow"></umb-icon>`;
default:
return;
}
}
render() {
return html`
<uui-box headline="Indexers" class="overview">
<uui-box headline=${this.localize.term('examineManagement_indexers')} class="overview">
<p>
<strong>Manage Examine's indexes</strong><br />
Allows you to view the details of each index and provides some tools for managing the indexes
<strong><umb-localize key="examineManagement_manageIndexes">Manage Examine's indexes</umb-localize></strong
><br />
<umb-localize key="examineManagement_manageIndexesDescription"
>Allows you to view the details of each index and provides some tools for managing the indexes</umb-localize
>
</p>
${this.renderIndexersList()}
</uui-box>
<uui-box headline="Searchers">
<uui-box headline=${this.localize.term('examineManagement_searchers')}>
<p>
<strong>Configured Searchers</strong><br />
Shows properties and tools for any configured Searcher (i.e. such as a multi-index searcher)
<strong><umb-localize key="examineManagement_configuredSearchers">Configured Searchers</umb-localize></strong
><br />
<umb-localize key="examineManagement_configuredSearchersDescription"
>Shows properties and tools for any configured Searcher (i.e. such as a multi-index searcher)</umb-localize
>
</p>
${this.renderSearchersList()}
</uui-box>
@@ -66,16 +85,7 @@ export class UmbDashboardExamineOverviewElement extends UmbLitElement {
${this._indexers.map((index) => {
return html`
<uui-table-row>
<uui-table-cell style="width:0px">
<uui-icon-essentials>
${
index.healthStatus === HealthStatusModel.UNHEALTHY
? html`<uui-icon name="wrong" class="danger"></uui-icon>`
: html`<uui-icon name="check" class="positive"></uui-icon>`
}
</uui-icon>
</uui-icon-essentials>
</uui-table-cell>
<uui-table-cell style="width:0px"> ${this.#renderStatus(index.healthStatus.status)} </uui-table-cell>
<uui-table-cell>
<a href="${window.location.href.replace(/\/+$/, '')}/index/${index.name}">${index.name}</a>
</uui-table-cell>

View File

@@ -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`
<uui-box headline="Search">
<p>Search the ${this.searcherName} and view the results</p>
<uui-box headline=${this.localize.term('general_search')}>
<p>
<umb-localize key="examineManagement_searchDescription"
>Search the ${this.searcherName} and view the results</umb-localize
>
</p>
<div class="flex">
<uui-input
type="search"
id="search-input"
placeholder="Type to filter..."
label="Type to filter"
placeholder=${this.localize.term('placeholders_filter')}
label=${this.localize.term('placeholders_filter')}
@keypress=${this._onKeyPress}
${umbFocus()}>
</uui-input>
<uui-button color="positive" look="primary" label="Search" @click="${this._onSearch}"> Search </uui-button>
<uui-button
color="positive"
look="primary"
label=${this.localize.term('general_search')}
@click="${this._onSearch}"></uui-button>
</div>
${this.renderSearchResults()}
</uui-box>
@@ -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`<uui-loader></uui-loader>`;
if (!this._searchResults) return nothing;
if (!this._searchResults.length) {
return html`<p>No results found</p>`;
return html`<p>${this.localize.term('examineManagement_noResults')}</p>`;
}
return html`<div class="table-container">
<uui-scroll-container>
<uui-table class="search">
<uui-table-head>
<uui-table-head-cell style="width:0">Score</uui-table-head-cell>
<uui-table-head-cell style="width:0">Id</uui-table-head-cell>
<uui-table-head-cell>Name</uui-table-head-cell>
<uui-table-head-cell>Fields</uui-table-head-cell>
<uui-table-head-cell style="width:0">${this.localize.term('general_id')}</uui-table-head-cell>
<uui-table-head-cell>${this.localize.term('general_name')}</uui-table-head-cell>
<uui-table-head-cell>${this.localize.term('examineManagement_fields')}</uui-table-head-cell>
${this.renderHeadCells()}
</uui-table-head>
${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`<uui-table-row>
<uui-table-cell> ${rowData.score} </uui-table-cell>
<uui-table-cell> ${rowData.id} </uui-table-cell>
<uui-table-cell>
<uui-button look="secondary" label="Open workspace for this document" @click="${this._onNameClick}">
<uui-button
look="secondary"
label=${this.localize.term('actions_editContent')}
href=${this._workspacePath + this.#entityType + '/edit/' + unique}>
${this.getSearchResultNodeName(rowData)}
</uui-button>
</uui-table-cell>
@@ -157,9 +199,10 @@ export class UmbDashboardExamineSearcherElement extends UmbLitElement {
<uui-button
class="bright"
look="secondary"
label="Open sidebar to see all fields"
label=${this.localize.term('examineManagement_fieldValues')}
@click=${() => this.#onFieldViewClick(rowData)}>
${rowData.fields ? Object.keys(rowData.fields).length : ''} fields
${rowData.fields ? Object.keys(rowData.fields).length : ''}
${this.localize.term('examineManagement_fields')}
</uui-button>
</uui-table-cell>
${rowData.fields ? this.renderBodyCells(rowData.fields) : ''}
@@ -185,7 +228,7 @@ export class UmbDashboardExamineSearcherElement extends UmbLitElement {
<span>${field.name}</span>
<uui-button
look="secondary"
label="Close field ${field.name}"
label="${this.localize.term('actions_remove')} ${field.name}"
compact
@click="${() => {
this._exposedFields = this._exposedFields?.map((f) => {

View File

@@ -1,5 +1,8 @@
import { manifests as examineManifests } from './examine-management-dashboard/manifests.js';
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
import './examine-management-dashboard/index.js';
export const manifests: Array<ManifestTypes> = [
{
type: 'headerApp',
@@ -37,10 +40,5 @@ export const manifests: Array<ManifestTypes> = [
},
],
},
{
type: 'modal',
alias: 'Umb.Modal.ExamineFieldsSettings',
name: 'Examine Field Settings Modal',
js: () => import('./examine-management-dashboard/views/modal-views/fields-settings-modal.element.js'),
},
...examineManifests,
];

View File

@@ -52,7 +52,7 @@ export class UmbInputWebhookEventsElement extends UmbLitElement {
(item) => item.alias,
(item) => html`
<span>${item.eventName}</span>
<uui-button @click=${() => this.#removeEvent(item.alias)} label="remove"></uui-button>
<uui-button label=${this.localize.term('general_remove')} @click=${() => this.#removeEvent(item.alias)}></uui-button>
`,
)}
`;
@@ -60,7 +60,7 @@ export class UmbInputWebhookEventsElement extends UmbLitElement {
render() {
return html`${this.#renderEvents()}
<uui-button id="add" look="placeholder" label="Add" @click=${this.#openModal}></uui-button>`;
<uui-button id="choose" look="placeholder" label=${this.localize.term('general_choose')} @click=${this.#openModal}></uui-button>`;
}
static styles = [
@@ -73,7 +73,7 @@ export class UmbInputWebhookEventsElement extends UmbLitElement {
align-items: center;
}
#add {
#choose {
grid-column: -1 / 1;
}
`,

View File

@@ -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",