Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './media-picker-folder-path.element.js';
export * from './media-picker-table-column-name.element.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { css, customElement, html, nothing, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbTableColumn, UmbTableColumnLayoutElement, UmbTableItem } from '@umbraco-cms/backoffice/components';

export interface UmbMediaPickerTableColumnNameValue {
name: string;
ancestorPath?: string;
/** When set, the name renders as a button that invokes this on click (e.g. to open a folder). */
navigate?: () => void;
}

@customElement('umb-media-picker-table-column-name')
export class UmbMediaPickerTableColumnNameElement extends UmbLitElement implements UmbTableColumnLayoutElement {
column!: UmbTableColumn;
item!: UmbTableItem;

@property({ attribute: false })
value!: UmbMediaPickerTableColumnNameValue;

#onClick(event: Event) {
event.stopPropagation();
this.value?.navigate?.();
}

override render() {
if (!this.value) return nothing;
return html`
${this.value.navigate
? html`<uui-button look="default" compact label=${this.value.name} @click=${this.#onClick}
>${this.value.name}</uui-button
>`
: html`<span class="name">${this.value.name}</span>`}
${this.value.ancestorPath
? html`<span class="ancestor-path">${this.value.ancestorPath}</span>`
: nothing}
`;
}

static override styles = [

Check warning on line 40 in src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/components/media-picker-table-column-name.element.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make this public static property readonly.

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AZ_LyP_tA8yQqB7E5TiC&open=AZ_LyP_tA8yQqB7E5TiC&pullRequest=23116
UmbTextStyles,
css`
:host {
display: block;
}

uui-button {
--uui-button-padding-left-factor: 0;
text-align: left;
margin-bottom: -9px;
}

Comment thread
AndyButland marked this conversation as resolved.
.name {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.ancestor-path {
display: block;
font-size: 0.8em;
opacity: 0.6;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
/* Truncate from the start so the deepest (most disambiguating) ancestor stays visible. */
direction: rtl;
text-align: left;
}
`,
];
}

export default UmbMediaPickerTableColumnNameElement;

declare global {
interface HTMLElementTagNameMap {
'umb-media-picker-table-column-name': UmbMediaPickerTableColumnNameElement;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { UmbMediaPickerTableColumnNameElement } from './media-picker-table-column-name.element.js';
import { expect, fixture, html } from '@open-wc/testing';
import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils';

describe('UmbMediaPickerTableColumnNameElement', () => {
let element: UmbMediaPickerTableColumnNameElement;

beforeEach(async () => {
element = await fixture(html`<umb-media-picker-table-column-name></umb-media-picker-table-column-name>`);
});

it('is defined with its own instance', () => {
expect(element).to.be.instanceOf(UmbMediaPickerTableColumnNameElement);
});

it('renders the name', async () => {
element.value = { name: 'My image' };
await element.updateComplete;
expect(element.shadowRoot?.textContent).to.contain('My image');
});

it('renders the ancestor path when provided', async () => {
element.value = { name: 'My image', ancestorPath: 'Media / Holiday' };
await element.updateComplete;
const path = element.shadowRoot?.querySelector('.ancestor-path');
expect(path).to.exist;
expect(path?.textContent).to.contain('Media / Holiday');
});

it('does not render an ancestor path when not provided', async () => {

Check failure on line 30 in src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/components/media-picker-table-column-name.test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add at least one assertion to this test case.

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AZ_LyP2HA8yQqB7E5Th-&open=AZ_LyP2HA8yQqB7E5Th-&pullRequest=23116
element.value = { name: 'My image' };
await element.updateComplete;
expect(element.shadowRoot?.querySelector('.ancestor-path')).to.not.exist;
});

it('renders the name as a plain span when not navigable', async () => {

Check failure on line 36 in src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/components/media-picker-table-column-name.test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add at least one assertion to this test case.

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AZ_LyP2HA8yQqB7E5Th_&open=AZ_LyP2HA8yQqB7E5Th_&pullRequest=23116
element.value = { name: 'My image' };
await element.updateComplete;
expect(element.shadowRoot?.querySelector('uui-button')).to.not.exist;
expect(element.shadowRoot?.querySelector('span.name')).to.exist;
});

it('renders the name as a button and invokes navigate on click', async () => {

Check failure on line 43 in src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/components/media-picker-table-column-name.test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add at least one assertion to this test case.

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AZ_LyP2HA8yQqB7E5TiA&open=AZ_LyP2HA8yQqB7E5TiA&pullRequest=23116
let navigated = false;
element.value = { name: 'A folder', navigate: () => (navigated = true) };
await element.updateComplete;

const button = element.shadowRoot?.querySelector('uui-button');
expect(button).to.exist;

button!.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true }));
expect(navigated).to.be.true;
});

it('stops the click from bubbling so it does not trigger row selection', async () => {

Check failure on line 55 in src/Umbraco.Web.UI.Client/src/packages/media/media/modals/media-picker/components/media-picker-table-column-name.test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add at least one assertion to this test case.

See more on https://sonarcloud.io/project/issues?id=umbraco_Umbraco-CMS&issues=AZ_LyP2HA8yQqB7E5TiB&open=AZ_LyP2HA8yQqB7E5TiB&pullRequest=23116
element.value = { name: 'A folder', navigate: () => {} };
await element.updateComplete;

const button = element.shadowRoot?.querySelector('uui-button');

let bubbledToHost = false;
element.addEventListener('click', () => (bubbledToHost = true));
button!.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true }));

expect(bubbledToHost).to.be.false;
});

if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) {
it('passes the a11y audit', async () => {
element.value = { name: 'My image', ancestorPath: 'Media / Holiday' };
await element.updateComplete;
await expect(element).shadowDom.to.be.accessible(defaultA11yConfig);
});
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { UmbMediaTreeItemModel, UmbMediaSearchItemModel, UmbMediaItemModel
import { UmbMediaPickerContext } from './media-picker.context.js';
import type { UmbMediaPathModel } from './types.js';
import type { UmbMediaPickerFolderPathElement } from './components/media-picker-folder-path.element.js';
import type { UmbMediaPickerTableColumnNameValue } from './components/media-picker-table-column-name.element.js';
import type { UmbMediaPickerModalData, UmbMediaPickerModalValue } from './media-picker-modal.token.js';
import {
css,
Expand Down Expand Up @@ -469,6 +470,11 @@ export class UmbMediaPickerModalElement extends UmbPickerModalBaseElement<
return this.#folderTypeUniques.has(item.mediaType.unique) || item.hasChildren;
}

#getAncestorPath(item: UmbMediaTreeItemModel | UmbMediaSearchItemModel): string {
const ancestors = 'ancestors' in item ? item.ancestors : undefined;
return ancestors?.length ? ancestors.map((a) => a.name || '(Untitled)').join(' / ') : '';
Comment thread
AndyButland marked this conversation as resolved.
}

// TODO: move to search manager in context
#onSearchFromChange(e: CustomEvent) {
const checked = (e.target as HTMLInputElement).checked;
Expand Down Expand Up @@ -669,26 +675,19 @@ export class UmbMediaPickerModalElement extends UmbPickerModalBaseElement<
// When not in selection mode, navigable items open on click (matching card behaviour).
// Mark them as non-selectable so the row doesn't intercept the click for selection.
const selectableInTable = this._isSelectionMode ? selectable : !canNavigate && selectable;
const nameColumnValue: UmbMediaPickerTableColumnNameValue = {
name: item.name,
ancestorPath: this.#getAncestorPath(item) || undefined,
navigate: canNavigate && !this._isSelectionMode ? () => this.#onOpen(item) : undefined,
};
return {
id: item.unique,
icon: item.mediaType.icon,
selectable: selectableInTable,
data: [
{
columnAlias: 'name',
value:
canNavigate && !this._isSelectionMode
? html`<uui-button
look="default"
compact
label=${item.name}
@click=${(e: Event) => {
e.stopPropagation();
this.#onOpen(item);
}}
>${item.name}</uui-button
>`
: html`<span class="table-name">${item.name}</span>`,
value: nameColumnValue,
},
{
columnAlias: 'createDate',
Expand Down Expand Up @@ -716,6 +715,7 @@ export class UmbMediaPickerModalElement extends UmbPickerModalBaseElement<
{
name: this.localize.term('general_name'),
alias: 'name',
elementName: 'umb-media-picker-table-column-name',
},
{
name: this.localize.term('content_createDate'),
Expand Down Expand Up @@ -815,13 +815,6 @@ export class UmbMediaPickerModalElement extends UmbPickerModalBaseElement<
margin-top: var(--uui-size-layout-1);
}

.table-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

#view-dropdown {
padding: var(--uui-size-space-3);
--uui-button-content-align: left;
Expand Down
Loading