20240904.0 (#21876)

This commit is contained in:
Bram Kragten 2024-09-04 10:59:24 +02:00 committed by GitHub
commit b457d27c4c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 355 additions and 281 deletions

View File

@ -15,23 +15,29 @@ const brotliOptions = {
};
const zopfliOptions = { threshold: 150 };
const compressDistBrotli = (rootDir, modernDir) =>
const compressDistBrotli = (rootDir, modernDir, compressServiceWorker = true) =>
gulp
.src([`${modernDir}/**/${filesGlob}`, `${rootDir}/sw-modern.js`], {
base: rootDir,
})
.src(
[
`${modernDir}/**/${filesGlob}`,
compressServiceWorker ? `${rootDir}/sw-modern.js` : undefined,
].filter(Boolean),
{
base: rootDir,
}
)
.pipe(brotli(brotliOptions))
.pipe(gulp.dest(rootDir));
const compressDistZopfli = (rootDir, modernDir) =>
const compressDistZopfli = (rootDir, modernDir, compressModern = false) =>
gulp
.src(
[
`${rootDir}/**/${filesGlob}`,
`!${modernDir}/**/${filesGlob}`,
compressModern ? undefined : `!${modernDir}/**/${filesGlob}`,
`!${rootDir}/{sw-modern,service_worker}.js`,
`${rootDir}/{authorize,onboarding}.html`,
],
].filter(Boolean),
{ base: rootDir }
)
.pipe(zopfli(zopfliOptions))
@ -40,12 +46,20 @@ const compressDistZopfli = (rootDir, modernDir) =>
const compressAppBrotli = () =>
compressDistBrotli(paths.app_output_root, paths.app_output_latest);
const compressHassioBrotli = () =>
compressDistBrotli(paths.hassio_output_root, paths.hassio_output_latest);
compressDistBrotli(
paths.hassio_output_root,
paths.hassio_output_latest,
false
);
const compressAppZopfli = () =>
compressDistZopfli(paths.app_output_root, paths.app_output_latest);
const compressHassioZopfli = () =>
compressDistZopfli(paths.hassio_output_root, paths.hassio_output_latest);
compressDistZopfli(
paths.hassio_output_root,
paths.hassio_output_latest,
true
);
gulp.task("compress-app", gulp.parallel(compressAppBrotli, compressAppZopfli));
gulp.task(

View File

@ -28,7 +28,7 @@
"@babel/runtime": "7.25.6",
"@braintree/sanitize-url": "7.1.0",
"@codemirror/autocomplete": "6.18.0",
"@codemirror/commands": "6.6.0",
"@codemirror/commands": "6.6.1",
"@codemirror/language": "6.10.2",
"@codemirror/legacy-modes": "6.4.1",
"@codemirror/search": "6.5.6",

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "home-assistant-frontend"
version = "20240903.1"
version = "20240904.0"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"

View File

@ -25,7 +25,6 @@ import { fireEvent } from "../../common/dom/fire_event";
import { stringCompare } from "../../common/string/compare";
import { debounce } from "../../common/util/debounce";
import { groupBy } from "../../common/util/group-by";
import { nextRender } from "../../common/util/render-status";
import { haStyleScrollbar } from "../../resources/styles";
import { loadVirtualizer } from "../../resources/virtualizer";
import { HomeAssistant } from "../../types";
@ -35,6 +34,7 @@ import "../ha-svg-icon";
import "../search-input";
import { filterData, sortData } from "./sort-filter";
import { LocalizeFunc } from "../../common/translations/localize";
import { nextRender } from "../../common/util/render-status";
export interface RowClickedEvent {
id: string;
@ -169,8 +169,6 @@ export class HaDataTable extends LitElement {
@query("slot[name='header']") private _header!: HTMLSlotElement;
@state() private _items: DataTableRowData[] = [];
@state() private _collapsedGroups: string[] = [];
private _checkableRowsCount?: number;
@ -179,7 +177,9 @@ export class HaDataTable extends LitElement {
private _sortColumns: SortableColumnContainer = {};
private curRequest = 0;
private _curRequest = 0;
private _lastUpdate = 0;
// @ts-ignore
@restoreScroll(".scroller") private _savedScrollPos?: number;
@ -206,9 +206,9 @@ export class HaDataTable extends LitElement {
public connectedCallback() {
super.connectedCallback();
if (this._items.length) {
if (this._filteredData.length) {
// Force update of location of rows
this._items = [...this._items];
this._filteredData = [...this._filteredData];
}
}
@ -291,16 +291,13 @@ export class HaDataTable extends LitElement {
properties.has("columns") ||
properties.has("_filter") ||
properties.has("sortColumn") ||
properties.has("sortDirection") ||
properties.has("groupColumn") ||
properties.has("groupOrder") ||
properties.has("_collapsedGroups")
properties.has("sortDirection")
) {
this._sortFilterData();
}
if (properties.has("selectable") || properties.has("hiddenColumns")) {
this._items = [...this._items];
this._filteredData = [...this._filteredData];
}
}
@ -467,7 +464,15 @@ export class HaDataTable extends LitElement {
scroller
class="mdc-data-table__content scroller ha-scrollbar"
@scroll=${this._saveScrollPos}
.items=${this._items}
.items=${this._groupData(
this._filteredData,
localize,
this.appendRow,
this.hasFab,
this.groupColumn,
this.groupOrder,
this._collapsedGroups
)}
.keyFunction=${this._keyFunction}
.renderItem=${renderRow}
></lit-virtualizer>
@ -602,8 +607,13 @@ export class HaDataTable extends LitElement {
private async _sortFilterData() {
const startTime = new Date().getTime();
this.curRequest++;
const curRequest = this.curRequest;
const timeBetweenUpdate = startTime - this._lastUpdate;
const timeBetweenRequest = startTime - this._curRequest;
this._curRequest = startTime;
const forceUpdate =
!this._lastUpdate ||
(timeBetweenUpdate > 500 && timeBetweenRequest < 500);
let filteredData = this.data;
if (this._filter) {
@ -614,6 +624,10 @@ export class HaDataTable extends LitElement {
);
}
if (!forceUpdate && this._curRequest !== startTime) {
return;
}
const prom = this.sortColumn
? sortData(
filteredData,
@ -634,91 +648,103 @@ export class HaDataTable extends LitElement {
setTimeout(resolve, 100 - elapsed);
});
}
if (this.curRequest !== curRequest) {
if (!forceUpdate && this._curRequest !== startTime) {
return;
}
const localize = this.localizeFunc || this.hass.localize;
if (this.appendRow || this.hasFab || this.groupColumn) {
let items = [...data];
if (this.groupColumn) {
const grouped = groupBy(items, (item) => item[this.groupColumn!]);
if (grouped.undefined) {
// make sure ungrouped items are at the bottom
grouped[UNDEFINED_GROUP_KEY] = grouped.undefined;
delete grouped.undefined;
}
const sorted: {
[key: string]: DataTableRowData[];
} = Object.keys(grouped)
.sort((a, b) => {
const orderA = this.groupOrder?.indexOf(a) ?? -1;
const orderB = this.groupOrder?.indexOf(b) ?? -1;
if (orderA !== orderB) {
if (orderA === -1) {
return 1;
}
if (orderB === -1) {
return -1;
}
return orderA - orderB;
}
return stringCompare(
["", "-", "—"].includes(a) ? "zzz" : a,
["", "-", "—"].includes(b) ? "zzz" : b,
this.hass.locale.language
);
})
.reduce((obj, key) => {
obj[key] = grouped[key];
return obj;
}, {});
const groupedItems: DataTableRowData[] = [];
Object.entries(sorted).forEach(([groupName, rows]) => {
groupedItems.push({
append: true,
content: html`<div
class="mdc-data-table__cell group-header"
role="cell"
.group=${groupName}
@click=${this._collapseGroup}
>
<ha-icon-button
.path=${mdiChevronUp}
class=${this._collapsedGroups.includes(groupName)
? "collapsed"
: ""}
>
</ha-icon-button>
${groupName === UNDEFINED_GROUP_KEY
? localize("ui.components.data-table.ungrouped")
: groupName || ""}
</div>`,
});
if (!this._collapsedGroups.includes(groupName)) {
groupedItems.push(...rows);
}
});
items = groupedItems;
}
if (this.appendRow) {
items.push({ append: true, content: this.appendRow });
}
if (this.hasFab) {
items.push({ empty: true });
}
this._items = items;
} else {
this._items = data;
}
this._lastUpdate = startTime;
this._filteredData = data;
}
private _groupData = memoizeOne(
(
data: DataTableRowData[],
localize: LocalizeFunc,
appendRow,
hasFab: boolean,
groupColumn: string | undefined,
groupOrder: string[] | undefined,
collapsedGroups: string[]
) => {
if (appendRow || hasFab || groupColumn) {
let items = [...data];
if (groupColumn) {
const grouped = groupBy(items, (item) => item[groupColumn]);
if (grouped.undefined) {
// make sure ungrouped items are at the bottom
grouped[UNDEFINED_GROUP_KEY] = grouped.undefined;
delete grouped.undefined;
}
const sorted: {
[key: string]: DataTableRowData[];
} = Object.keys(grouped)
.sort((a, b) => {
const orderA = groupOrder?.indexOf(a) ?? -1;
const orderB = groupOrder?.indexOf(b) ?? -1;
if (orderA !== orderB) {
if (orderA === -1) {
return 1;
}
if (orderB === -1) {
return -1;
}
return orderA - orderB;
}
return stringCompare(
["", "-", "—"].includes(a) ? "zzz" : a,
["", "-", "—"].includes(b) ? "zzz" : b,
this.hass.locale.language
);
})
.reduce((obj, key) => {
obj[key] = grouped[key];
return obj;
}, {});
const groupedItems: DataTableRowData[] = [];
Object.entries(sorted).forEach(([groupName, rows]) => {
groupedItems.push({
append: true,
content: html`<div
class="mdc-data-table__cell group-header"
role="cell"
.group=${groupName}
@click=${this._collapseGroup}
>
<ha-icon-button
.path=${mdiChevronUp}
class=${collapsedGroups.includes(groupName)
? "collapsed"
: ""}
>
</ha-icon-button>
${groupName === UNDEFINED_GROUP_KEY
? localize("ui.components.data-table.ungrouped")
: groupName || ""}
</div>`,
});
if (!collapsedGroups.includes(groupName)) {
groupedItems.push(...rows);
}
});
items = groupedItems;
}
if (appendRow) {
items.push({ append: true, content: appendRow });
}
if (hasFab) {
items.push({ empty: true });
}
return items;
}
return data;
}
);
private _memFilterData = memoizeOne(
(
data: DataTableRowData[],
@ -802,8 +828,8 @@ export class HaDataTable extends LitElement {
private _checkedRowsChanged() {
// force scroller to update, change it's items
if (this._items.length) {
this._items = [...this._items];
if (this._filteredData.length) {
this._filteredData = [...this._filteredData];
}
fireEvent(this, "selection-changed", {
value: this._checkedRows,

155
src/components/ha-badge.ts Normal file
View File

@ -0,0 +1,155 @@
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import "./ha-ripple";
type BadgeType = "badge" | "button";
@customElement("ha-badge")
export class HaBadge extends LitElement {
@property() public type: BadgeType = "badge";
@property() public label?: string;
@property({ type: Boolean, attribute: "icon-only" }) iconOnly = false;
protected render() {
const label = this.label;
return html`
<div
class="badge ${classMap({
"icon-only": this.iconOnly,
})}"
role=${ifDefined(this.type === "button" ? "button" : undefined)}
tabindex=${ifDefined(this.type === "button" ? "0" : undefined)}
>
<ha-ripple .disabled=${this.type !== "button"}></ha-ripple>
<slot name="icon"></slot>
${this.iconOnly
? nothing
: html`<span class="info">
${label ? html`<span class="label">${label}</span>` : nothing}
<span class="content"><slot></slot></span>
</span>`}
</div>
`;
}
static get styles(): CSSResultGroup {
return css`
:host {
--badge-color: var(--secondary-text-color);
-webkit-tap-highlight-color: transparent;
}
.badge {
position: relative;
--ha-ripple-color: var(--badge-color);
--ha-ripple-hover-opacity: 0.04;
--ha-ripple-pressed-opacity: 0.12;
transition:
box-shadow 180ms ease-in-out,
border-color 180ms ease-in-out;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 8px;
height: var(--ha-badge-size, 36px);
min-width: var(--ha-badge-size, 36px);
padding: 0px 12px;
box-sizing: border-box;
width: auto;
border-radius: var(
--ha-badge-border-radius,
calc(var(--ha-badge-size, 36px) / 2)
);
background: var(
--ha-card-background,
var(--card-background-color, white)
);
-webkit-backdrop-filter: var(--ha-card-backdrop-filter, none);
backdrop-filter: var(--ha-card-backdrop-filter, none);
border-width: var(--ha-card-border-width, 1px);
box-shadow: var(--ha-card-box-shadow, none);
border-style: solid;
border-color: var(
--ha-card-border-color,
var(--divider-color, #e0e0e0)
);
}
.badge:focus-visible {
--shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent);
--shadow-focus: 0 0 0 1px var(--badge-color);
border-color: var(--badge-color);
box-shadow: var(--shadow-default), var(--shadow-focus);
}
[role="button"] {
cursor: pointer;
}
[role="button"]:focus {
outline: none;
}
.info {
display: flex;
flex-direction: column;
align-items: flex-start;
padding-inline-start: initial;
text-align: center;
font-family: Roboto;
}
.label {
font-size: 10px;
font-style: normal;
font-weight: 500;
line-height: 10px;
letter-spacing: 0.1px;
color: var(--secondary-text-color);
}
.content {
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 16px;
letter-spacing: 0.1px;
color: var(--primary-text-color);
}
::slotted([slot="icon"]) {
--mdc-icon-size: 18px;
color: var(--badge-color);
line-height: 0;
margin-left: -4px;
margin-right: 0;
margin-inline-start: -4px;
margin-inline-end: 0;
}
::slotted(img[slot="icon"]) {
width: 30px;
height: 30px;
border-radius: 50%;
object-fit: cover;
overflow: hidden;
margin-left: -10px;
margin-right: 0;
margin-inline-start: -10px;
margin-inline-end: 0;
}
.badge.icon-only {
padding: 0;
}
.badge.icon-only ::slotted([slot="icon"]) {
margin-left: 0;
margin-right: 0;
margin-inline-start: 0;
margin-inline-end: 0;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-badge": HaBadge;
}
}

View File

@ -3,7 +3,6 @@ import { HassEntity } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { computeCssColor } from "../../../common/color/compute-color";
@ -12,6 +11,7 @@ import { computeDomain } from "../../../common/entity/compute_domain";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { stateActive } from "../../../common/entity/state_active";
import { stateColorCss } from "../../../common/entity/state_color";
import "../../../components/ha-badge";
import "../../../components/ha-ripple";
import "../../../components/ha-state-icon";
import "../../../components/ha-svg-icon";
@ -160,15 +160,14 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
if (!stateObj) {
return html`
<div class="badge error">
<ha-svg-icon .hass=${this.hass} .path=${mdiAlertCircle}></ha-svg-icon>
<span class="info">
<span class="label">${entityId}</span>
<span class="content">
${this.hass.localize("ui.badge.entity.not_found")}
</span>
</span>
</div>
<ha-badge .label=${entityId} class="error">
<ha-svg-icon
slot="icon"
.hass=${this.hass}
.path=${mdiAlertCircle}
></ha-svg-icon>
${this.hass.localize("ui.badge.entity.not_found")}
</ha-badge>
`;
}
@ -204,42 +203,32 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
const content = showState ? stateDisplay : showName ? name : undefined;
return html`
<div
style=${styleMap(style)}
class="badge ${classMap({
active,
"no-info": !showState && !showName,
"no-icon": !showIcon,
})}"
<ha-badge
.type=${this.hasAction ? "button" : "badge"}
@action=${this._handleAction}
.actionHandler=${actionHandler({
hasHold: hasAction(this._config!.hold_action),
hasDoubleClick: hasAction(this._config!.double_tap_action),
})}
role=${ifDefined(this.hasAction ? "button" : undefined)}
tabindex=${ifDefined(this.hasAction ? "0" : undefined)}
.label=${label}
.iconOnly=${!content}
style=${styleMap(style)}
class=${classMap({ active })}
>
<ha-ripple .disabled=${!this.hasAction}></ha-ripple>
${showIcon
? imageUrl
? html`<img src=${imageUrl} aria-hidden />`
? html`<img slot="icon" src=${imageUrl} aria-hidden />`
: html`
<ha-state-icon
slot="icon"
.hass=${this.hass}
.stateObj=${stateObj}
.icon=${this._config.icon}
></ha-state-icon>
`
: nothing}
${content
? html`
<span class="info">
${label ? html`<span class="label">${name}</span>` : nothing}
<span class="content">${content}</span>
</span>
`
: nothing}
</div>
${content}
</ha-badge>
`;
}
@ -249,119 +238,15 @@ export class HuiEntityBadge extends LitElement implements LovelaceBadge {
static get styles(): CSSResultGroup {
return css`
:host {
ha-badge {
--badge-color: var(--state-inactive-color);
-webkit-tap-highlight-color: transparent;
}
.badge.error {
ha-badge.error {
--badge-color: var(--red-color);
}
.badge {
position: relative;
--ha-ripple-color: var(--badge-color);
--ha-ripple-hover-opacity: 0.04;
--ha-ripple-pressed-opacity: 0.12;
transition:
box-shadow 180ms ease-in-out,
border-color 180ms ease-in-out;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 8px;
height: var(--ha-badge-size, 36px);
min-width: var(--ha-badge-size, 36px);
padding: 0px 8px;
box-sizing: border-box;
width: auto;
border-radius: var(
--ha-badge-border-radius,
calc(var(--ha-badge-size, 36px) / 2)
);
background: var(
--ha-card-background,
var(--card-background-color, white)
);
-webkit-backdrop-filter: var(--ha-card-backdrop-filter, none);
backdrop-filter: var(--ha-card-backdrop-filter, none);
border-width: var(--ha-card-border-width, 1px);
box-shadow: var(--ha-card-box-shadow, none);
border-style: solid;
border-color: var(
--ha-card-border-color,
var(--divider-color, #e0e0e0)
);
--mdc-icon-size: 18px;
text-align: center;
font-family: Roboto;
}
.badge:focus-visible {
--shadow-default: var(--ha-card-box-shadow, 0 0 0 0 transparent);
--shadow-focus: 0 0 0 1px var(--badge-color);
border-color: var(--badge-color);
box-shadow: var(--shadow-default), var(--shadow-focus);
}
button,
[role="button"] {
cursor: pointer;
}
button:focus,
[role="button"]:focus {
outline: none;
}
.badge.active {
ha-badge.active {
--badge-color: var(--primary-color);
}
.info {
display: flex;
flex-direction: column;
align-items: flex-start;
padding-right: 4px;
padding-inline-end: 4px;
padding-inline-start: initial;
}
.label {
font-size: 10px;
font-style: normal;
font-weight: 500;
line-height: 10px;
letter-spacing: 0.1px;
color: var(--secondary-text-color);
}
.content {
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 16px;
letter-spacing: 0.1px;
color: var(--primary-text-color);
}
ha-state-icon,
ha-svg-icon {
color: var(--badge-color);
line-height: 0;
}
img {
width: 30px;
height: 30px;
border-radius: 50%;
object-fit: cover;
overflow: hidden;
}
.badge.no-info {
padding: 0;
}
.badge:not(.no-icon):not(.no-info) img {
margin-left: -6px;
margin-inline-start: -6px;
margin-inline-end: initial;
}
.badge.no-icon .info {
padding-right: 4px;
padding-left: 4px;
padding-inline-end: 4px;
padding-inline-start: 4px;
}
`;
}
}

View File

@ -2,12 +2,11 @@ import { mdiAlertCircle } from "@mdi/js";
import { dump } from "js-yaml";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, state } from "lit/decorators";
import "../../../components/ha-label-badge";
import "../../../components/ha-badge";
import "../../../components/ha-svg-icon";
import { HomeAssistant } from "../../../types";
import { showAlertDialog } from "../custom-card-helpers";
import { LovelaceBadge } from "../types";
import { HuiEntityBadge } from "./hui-entity-badge";
import { ErrorBadgeConfig } from "./types";
export const createErrorBadgeElement = (config) => {
@ -55,41 +54,36 @@ export class HuiErrorBadge extends LitElement implements LovelaceBadge {
}
return html`
<button class="badge error" @click=${this._viewDetail}>
<ha-svg-icon .hass=${this.hass} .path=${mdiAlertCircle}></ha-svg-icon>
<ha-ripple></ha-ripple>
<span class="content">
<span class="name">Error</span>
<span class="state">${this._config.error}</span>
</span>
</button>
<ha-badge
class="error"
@click=${this._viewDetail}
type="button"
label="Error"
>
<ha-svg-icon slot="icon" .path=${mdiAlertCircle}></ha-svg-icon>
<div class="content">${this._config.error}</div>
</ha-badge>
`;
}
static get styles(): CSSResultGroup {
return [
HuiEntityBadge.styles,
css`
.badge.error {
--badge-color: var(--error-color);
border-color: var(--badge-color);
}
ha-svg-icon {
color: var(--badge-color);
}
.state {
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
pre {
font-family: var(--code-font-family, monospace);
white-space: break-spaces;
user-select: text;
}
`,
];
return css`
ha-badge {
--badge-color: var(--error-color);
--ha-card-border-color: var(--error-color);
}
.content {
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
pre {
font-family: var(--code-font-family, monospace);
white-space: break-spaces;
user-select: text;
}
`;
}
}

View File

@ -1458,15 +1458,15 @@ __metadata:
languageName: node
linkType: hard
"@codemirror/commands@npm:6.6.0":
version: 6.6.0
resolution: "@codemirror/commands@npm:6.6.0"
"@codemirror/commands@npm:6.6.1":
version: 6.6.1
resolution: "@codemirror/commands@npm:6.6.1"
dependencies:
"@codemirror/language": "npm:^6.0.0"
"@codemirror/state": "npm:^6.4.0"
"@codemirror/view": "npm:^6.27.0"
"@lezer/common": "npm:^1.1.0"
checksum: 10/e984511b8ac06a1afe01005a50af7a0b6765cf350547d281a13aedbcccbc8620187c6dce55caa0f7c8ba409771e4abb4aa3bae824b6870249c8176b57cc4a42e
checksum: 10/533f33db08a4ff7e400ff61e48d34525afdc894f92442a5bf469ed24b1992a61bf726b61647e2faa552a2f4703c149c92026d9a1f9169f615582cdfc36fcb010
languageName: node
linkType: hard
@ -8922,7 +8922,7 @@ __metadata:
"@braintree/sanitize-url": "npm:7.1.0"
"@bundle-stats/plugin-webpack-filter": "npm:4.15.0"
"@codemirror/autocomplete": "npm:6.18.0"
"@codemirror/commands": "npm:6.6.0"
"@codemirror/commands": "npm:6.6.1"
"@codemirror/language": "npm:6.10.2"
"@codemirror/legacy-modes": "npm:6.4.1"
"@codemirror/search": "npm:6.5.6"