Add localization of user(s) and usergroup(s)

This commit is contained in:
Filip Bech-Larsen
2023-09-21 15:28:16 +02:00
parent 2b4f414b37
commit 41a6676a5c
10 changed files with 112 additions and 95 deletions

View File

@@ -61,7 +61,7 @@ export class UmbInputListBaseElement extends UmbLitElement {
protected renderButton() {
return html`<uui-button id="add-button" look="placeholder" @click=${this._openPicker} label="open">
Add
${this.localize.term('general_add')}
</uui-button>`;
}
protected renderContent() {

View File

@@ -26,8 +26,8 @@ export class UmbUserGroupCollectionHeaderElement extends UmbLitElement {
render() {
return html`
<uui-button @click=${this.#onCreate} label="Create group" look="outline"></uui-button>
<uui-input @input=${this.#onSearch} label="search" id="input-search"></uui-input>
<uui-button @click=${this.#onCreate} label=${this.localize.term('actions_createGroup')} look="outline"></uui-button>
<uui-input @input=${this.#onSearch} label=${this.localize.term('general_search')} placeholder=${this.localize.term('visuallyHiddenTexts_userGroupSearchLabel')} id="input-search"></uui-input>
`;
}
static styles = [

View File

@@ -26,21 +26,21 @@ export class UmbUserGroupCollectionViewElement extends UmbLitElement {
@state()
private _tableColumns: Array<UmbTableColumn> = [
{
name: 'Name',
name: this.localize.term('general_name'),
alias: 'userGroupName',
elementName: 'umb-user-group-table-name-column-layout',
},
{
name: 'Sections',
name: this.localize.term('main_sections'),
alias: 'userGroupSections',
elementName: 'umb-user-group-table-sections-column-layout',
},
{
name: 'Content start node',
name: this.localize.term('user_startnode'),
alias: 'userGroupContentStartNode',
},
{
name: 'Media start node',
name: this.localize.term('user_mediastartnode'),
alias: 'userGroupMediaStartNode',
},
];
@@ -87,11 +87,11 @@ export class UmbUserGroupCollectionViewElement extends UmbLitElement {
},
{
columnAlias: 'userGroupContentStartNode',
value: userGroup.documentStartNodeId || 'Content root',
value: userGroup.documentStartNodeId || this.localize.term('content_contentRoot'),
},
{
columnAlias: 'userGroupMediaStartNode',
value: userGroup.mediaStartNodeId || 'Media root',
value: userGroup.mediaStartNodeId || this.localize.term('media_mediaRoot'),
},
],
};

View File

@@ -98,7 +98,7 @@ export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement {
</a>
<uui-input
id="name"
label="name"
label=${this.localize.term('general_name')}
.value=${this._userGroup?.name ?? ''}
@input="${this.#onNameChange}"></uui-input>
</div>
@@ -109,52 +109,55 @@ export class UmbUserGroupWorkspaceEditorElement extends UmbLitElement {
if (!this._userGroup) return nothing;
return html` <uui-box>
<div slot="headline">Assign access</div>
<umb-workspace-property-layout label="Sections" description="Add sections to give users access">
<div slot="headline"><umb-localize key="user_assignAccess"></umb-localize></div>
<umb-workspace-property-layout label=${this.localize.term('main_sections')} description=${this.localize.term('user_sectionsHelp')}>
<umb-input-section
slot="editor"
.value=${this._userGroup.sections ?? []}
@change=${(e: any) => this.#onSectionsChange(e.target.value)}></umb-input-section>
</umb-workspace-property-layout>
<umb-workspace-property-layout
label="Content start node"
description="Limit the content tree to a specific start node">
label=${this.localize.term('defaultdialogs_selectContentStartNode')}
description=${this.localize.term('user_startnodehelp')}>
<b slot="editor">CONTENT START NODE PICKER NOT IMPLEMENTED YET</b>
</umb-workspace-property-layout>
<umb-workspace-property-layout
label="Media start node"
description="Limit the media library to a specific start node">
label=${this.localize.term('defaultdialogs_selectMediaStartNode')}
description=${this.localize.term('user_mediastartnodehelp')}>
<b slot="editor">MEDIA START NODE PICKER NOT IMPLEMENTED YET</b>
</umb-workspace-property-layout>
</uui-box>
<uui-box>
<div slot="headline">Default Permissions</div>
<div slot="headline"><umb-localize key="user_permissionsDefault"></umb-localize></div>
<b>PERMISSIONS NOT IMPLEMENTED YET</b>
</uui-box>
<uui-box>
<div slot="headline">Granular permissions</div>
<div slot="headline"><umb-localize key="user_permissionsGranular"></umb-localize></div>
<b>PERMISSIONS NOT IMPLEMENTED YET</b>
</uui-box>`;
}
#renderRightColumn() {
return html`<uui-box>
<div slot="headline">Users</div>
<div slot="headline"><umb-localize key="sections_users"></umb-localize></div>
<!-- change any to UmbUserInputElement when package is available -->
<umb-user-input
@change=${(e: Event) => this.#onUsersChange((e.target as any).selectedIds)}
.selectedIds=${this._userKeys ?? []}></umb-user-input>
</uui-box>
<uui-box>
<div slot="headline">Delete user group</div>
<div slot="headline">
<umb-localize key="actions_delete"></umb-localize>
<umb-localize key="user_usergroup"></umb-localize>
</div>
<uui-button
@click=${this.#onDelete}
style="width: 100%"
color="danger"
look="secondary"
label="Delete"></uui-button>
label=${this.localize.term('actions_delete')}></uui-button>
</uui-box>`;
}

View File

@@ -117,20 +117,21 @@ export class UmbUserCollectionHeaderElement extends UmbLitElement {
return html`
<uui-button
@click=${this._showInviteOrCreate}
label=${this._isCloud ? 'Invite' : 'Create' + ' user'}
label=${this.localize.term(this._isCloud ? 'user_inviteUser' : 'user_createUser')}
look="outline"></uui-button>
<uui-input @input=${this._updateSearch} label="search" id="input-search"></uui-input>
<uui-input @input=${this._updateSearch} label=${this.localize.term('visuallyHiddenTexts_userSearchLabel')} placeholder=${this.localize.term('visuallyHiddenTexts_userSearchLabel')} id="input-search"></uui-input>
<div>
<!-- TODO: we should consider using the uui-combobox. We need to add a multiple options to it first -->
<umb-dropdown margin="8">
<uui-button @click=${this.#onDropdownClick} slot="trigger" label="status">
State: ${this._stateFilterSelection}
<umb-localize key="general_status"></umb-localize>:
<umb-localize key=${'user_state'+this._stateFilterSelection}></umb-localize>
</uui-button>
<div slot="dropdown" class="filter-dropdown">
${this._stateFilterOptions.map(
(option) =>
html`<uui-checkbox
label=${option}
label=${this.localize.term('user_state'+option)}
@change=${this.#onStateFilterChange}
name="state"
value=${option}></uui-checkbox>`
@@ -140,19 +141,25 @@ export class UmbUserCollectionHeaderElement extends UmbLitElement {
<!-- TODO: we should consider using the uui-combobox. We need to add a multiple options to it first -->
<umb-dropdown margin="8">
<uui-button @click=${this.#onDropdownClick} slot="trigger" label="order by"> Group: </uui-button>
<uui-button @click=${this.#onDropdownClick} slot="trigger" label=${this.localize.term('general_groups')}>
<umb-localize key="general_groups"></umb-localize>:
<!-- TODO: show the value here -->
</uui-button>
<div slot="dropdown" class="filter-dropdown">
<uui-checkbox label="Active"></uui-checkbox>
<uui-checkbox label="Inactive"></uui-checkbox>
<uui-checkbox label="Invited"></uui-checkbox>
<uui-checkbox label="Disabled"></uui-checkbox>
<!-- TODO: GET THESE FROM SERVER (not localized) -->
<uui-checkbox label="Administrators"></uui-checkbox>
<uui-checkbox label="Editors"></uui-checkbox>
<uui-checkbox label="Sensitive Data"></uui-checkbox>
<uui-checkbox label="Translators"></uui-checkbox>
<uui-checkbox label="Writers"></uui-checkbox>
</div>
</umb-dropdown>
<!-- TODO: we should consider using the uui-combobox. We need to add a multiple options to it first -->
<umb-dropdown margin="8">
<uui-button @click=${this.#onDropdownClick} slot="trigger" label="Order By">
Order By: <b>${this._orderBy}</b>
<uui-button @click=${this.#onDropdownClick} slot="trigger" label=${this.localize.term('general_orderBy')}>
<umb-localize key="general_orderBy"></umb-localize>:
<b>${this._orderBy}</b>
</uui-button>
<div slot="dropdown" class="filter-dropdown" name="orderBy">
<uui-radio-group name="radioGroup" @change=${this.#onOrderByChange}>

View File

@@ -1,4 +1,4 @@
import { getLookAndColorFromUserStatus } from '../../../../utils.js';
import { getDisplayStateFromUserStatus } from '../../../../utils.js';
import { UmbUserCollectionContext } from '../../user-collection.context.js';
import { type UmbUserDetail } from '../../../types.js';
import { css, html, nothing, customElement, state, repeat, ifDefined } from '@umbraco-cms/backoffice/external/lit';
@@ -63,24 +63,24 @@ export class UmbUserCollectionGridViewElement extends UmbLitElement {
return nothing;
}
const statusLook = getLookAndColorFromUserStatus(user.state);
const statusLook = getDisplayStateFromUserStatus(user.state);
return html`<uui-tag
slot="tag"
size="s"
look="${ifDefined(statusLook?.look)}"
color="${ifDefined(statusLook?.color)}">
${user.state}
<umb-localize key=${'user_'+statusLook.key}></umb-localize>
</uui-tag>`;
}
#renderUserLoginDate(user: UmbUserDetail) {
if (!user.lastLoginDate) {
return html`<div class="user-login-time">${`${user.name} has not logged in yet`}</div>`;
return html`<div class="user-login-time">${`${user.name} ${this.localize.term('user_noLogin')}`}</div>`;
}
return html`<div class="user-login-time">
<div>Last login</div>
${user.lastLoginDate}
<umb-localize key="user_lastLogin"></umb-localize><br/>
${this.localize.date(user.lastLoginDate)}
</div>`;
}

View File

@@ -1,4 +1,4 @@
import { getLookAndColorFromUserStatus } from '../../../../../../utils.js';
import { getDisplayStateFromUserStatus } from '../../../../../../utils.js';
import { html, LitElement, nothing, customElement, property } from '@umbraco-cms/backoffice/external/lit';
@customElement('umb-user-table-status-column-layout')
@@ -10,8 +10,8 @@ export class UmbUserTableStatusColumnLayoutElement extends LitElement {
return html`${this.value.status && this.value.status !== 'enabled'
? html`<uui-tag
size="s"
look="${getLookAndColorFromUserStatus(this.value.status).look}"
color="${getLookAndColorFromUserStatus(this.value.status).color}">
look="${getDisplayStateFromUserStatus(this.value.status).look}"
color="${getDisplayStateFromUserStatus(this.value.status).color}">
${this.value.status}
</uui-tag>`
: nothing}`;

View File

@@ -1,4 +1,4 @@
import { getLookAndColorFromUserStatus } from '../../utils.js';
import { getDisplayStateFromUserStatus } from '../../utils.js';
import { UmbUserRepository } from '../repository/user.repository.js';
import { UmbUserGroupInputElement } from '../../user-groups/components/input-user-group/user-group-input.element.js';
import { type UmbUserDetail } from '../index.js';
@@ -197,23 +197,23 @@ export class UmbUserWorkspaceEditorElement extends UmbLitElement {
return html` <uui-box>
<div slot="headline"><umb-localize key="user_profile">Profile</umb-localize></div>
<umb-workspace-property-layout label="Email">
<uui-input slot="editor" name="email" label="email" readonly value=${ifDefined(this._user.email)}></uui-input>
<umb-workspace-property-layout label="${this.localize.term('general_email')}">
<uui-input slot="editor" name="email" label="${this.localize.term('general_email')}" readonly value=${ifDefined(this._user.email)}></uui-input>
</umb-workspace-property-layout>
<umb-workspace-property-layout label="Language" description="The language of the UI in the Backoffice">
<umb-workspace-property-layout label="${this.localize.term('user_language')}" description=${this.localize.term('user_languageHelp')}>
<uui-select
slot="editor"
name="language"
label="language"
label="${this.localize.term('user_language')}"
.options=${this.languages}
@change="${this.#onLanguageChange}">
</uui-select>
</umb-workspace-property-layout>
</uui-box>
<uui-box>
<div slot="headline">Assign access</div>
<div slot="headline"><umb-localize key="user_assignAccess">Assign Access</umb-localize></div>
<div id="assign-access">
<umb-workspace-property-layout label="Groups" description="Add groups to assign access and permissions">
<umb-workspace-property-layout label="${this.localize.term('general_groups')}" description="${this.localize.term('user_groupsHelp')}">
<umb-user-group-input
slot="editor"
.selectedIds=${this._user.userGroupIds ?? []}
@@ -221,8 +221,8 @@ export class UmbUserWorkspaceEditorElement extends UmbLitElement {
this.#onUserGroupsChange((e.target as UmbUserGroupInputElement).selectedIds)}></umb-user-group-input>
</umb-workspace-property-layout>
<umb-workspace-property-layout
label="Content start node"
description="Limit the content tree to specific start nodes">
label=${this.localize.term('user_startnodes')}
description=${this.localize.term('user_startnodeshelp')}>
<umb-property-editor-ui-document-picker
.value=${this._user.contentStartNodeIds ?? []}
@property-value-change=${(e: any) =>
@@ -230,21 +230,22 @@ export class UmbUserWorkspaceEditorElement extends UmbLitElement {
slot="editor"></umb-property-editor-ui-document-picker>
</umb-workspace-property-layout>
<umb-workspace-property-layout
label="Media start nodes"
description="Limit the media library to specific start nodes">
label=${this.localize.term('user_mediastartnodes')}
description=${this.localize.term('user_mediastartnodeshelp')}>
<b slot="editor">NEED MEDIA PICKER</b>
</umb-workspace-property-layout>
</div>
</uui-box>
<uui-box headline="Access">
<uui-box headline=${this.localize.term('user_access')}>
<div slot="header" class="faded-text">
Based on the assigned groups and start nodes, the user has access to the following nodes
<umb-localize key="user_accessHelp">Based on the assigned groups and start nodes, the user has access to the following nodes</umb-localize>
</div>
<b>Content</b>
<b><umb-localize key="sections_content">Content</umb-localize></b>
${this.#renderContentStartNodes()}
<hr />
<b>Media</b>
<b><umb-localize key="sections_media">Media</umb-localize></b>
<uui-ref-node name="Media Root">
<uui-icon slot="icon" name="folder"></uui-icon>
</uui-ref-node>
@@ -254,49 +255,49 @@ export class UmbUserWorkspaceEditorElement extends UmbLitElement {
#renderRightColumn() {
if (!this._user || !this.#workspaceContext) return nothing;
const statusLook = getLookAndColorFromUserStatus(this._user.state);
const displayState = getDisplayStateFromUserStatus(this._user.state);
return html` <uui-box>
<div id="user-info">
<uui-avatar .name=${this._user?.name || ''}></uui-avatar>
<uui-button label="Change photo"></uui-button>
<uui-button label=${this.localize.term('user_changePhoto')}></uui-button>
<hr />
${this.#renderActionButtons()}
<div>
<b>Status:</b>
<uui-tag look="${ifDefined(statusLook?.look)}" color="${ifDefined(statusLook?.color)}">
${this._user.state}
<b><umb-localize key="general_status">Status</umb-localize>:</b>
<uui-tag look="${ifDefined(displayState?.look)}" color="${ifDefined(displayState?.color)}">
${this.localize.term('user_'+displayState.key)}
</uui-tag>
</div>
${this._user?.state === UserStateModel.INVITED
? html`
<uui-textarea placeholder="Enter a message..."> </uui-textarea>
<uui-button look="primary" label="Resend invitation"></uui-button>
<uui-textarea placeholder=${this.localize.term('placeholders_enterMessage')}></uui-textarea>
<uui-button look="primary" label=${this.localize.term('actions_resendInvite')}></uui-button>
`
: nothing}
${this.#renderInfoItem('Last login', this._user.lastLoginDate || `${this._user.name} has not logged in yet`)}
${this.#renderInfoItem('Failed login attempts', this._user.failedLoginAttempts)}
${this.#renderInfoItem('user_lastLogin', this.localize.date(this._user.lastLoginDate!) || `${this._user.name + ' ' + this.localize.term('user_noLogin') } `)}
${this.#renderInfoItem('user_failedPasswordAttempts', this._user.failedLoginAttempts)}
${this.#renderInfoItem(
'Last lockout date',
this._user.lastLockoutDate || `${this._user.name} has not been locked out`,
'user_lastLockoutDate',
this._user.lastLockoutDate || `${this._user.name + ' ' + this.localize.term('user_noLockouts')}`,
)}
${this.#renderInfoItem(
'Password last changed',
this._user.lastLoginDate || `${this._user.name} has not changed password`,
'user_lastPasswordChangeDate',
this._user.lastLoginDate || `${this._user.name + ' ' + this.localize.term('user_noPasswordChange')}`,
)}
${this.#renderInfoItem('User created', this._user.createDate)}
${this.#renderInfoItem('User last updated', this._user.updateDate)}
${this.#renderInfoItem('Key', this._user.id)}
${this.#renderInfoItem('user_createDate', this.localize.date(this._user.createDate!))}
${this.#renderInfoItem('user_updateDate', this.localize.date(this._user.updateDate!))}
${this.#renderInfoItem('general_id', this._user.id)}
</div>
</uui-box>`;
}
#renderInfoItem(label: string, value?: string | number) {
#renderInfoItem(labelkey: string, value?: string | number) {
return html`
<div>
<b>${label}</b>
<b><umb-localize key=${labelkey}></umb-localize></b>
<span>${value}</span>
</div>
`;
@@ -312,26 +313,26 @@ export class UmbUserWorkspaceEditorElement extends UmbLitElement {
if (this._user.state === UserStateModel.DISABLED) {
buttons.push(html`
<uui-button @click=${this.#onUserStatusChange} look="secondary" color="positive" label="Enable"></uui-button>
<uui-button @click=${this.#onUserStatusChange} look="secondary" color="positive" label=${this.localize.term('actions_enable')}></uui-button>
`);
}
if (this._user.state === UserStateModel.ACTIVE || this._user.state === UserStateModel.INACTIVE) {
buttons.push(html`
<uui-button @click=${this.#onUserStatusChange} look="secondary" color="warning" label="Disable"></uui-button>
<uui-button @click=${this.#onUserStatusChange} look="secondary" color="warning" label=${this.localize.term('actions_disable')}></uui-button>
`);
}
if (this._currentUser?.id !== this._user?.id) {
const button = html`
<uui-button @click=${this.#onUserDelete} look="secondary" color="danger" label="Delete User"></uui-button>
<uui-button @click=${this.#onUserDelete} look="secondary" color="danger" label=${this.localize.term('user_deleteUser')}></uui-button>
`;
buttons.push(button);
}
buttons.push(
html`<uui-button @click=${this.#onPasswordChange} look="secondary" label="Change password"></uui-button>`,
html`<uui-button @click=${this.#onPasswordChange} look="secondary" label=${this.localize.term('general_changePassword')}></uui-button>`,
);
return buttons;

View File

@@ -1,5 +1,5 @@
import { expect } from '@open-wc/testing';
import { getLookAndColorFromUserStatus } from './utils.js';
import { getDisplayStateFromUserStatus } from './utils.js';
import { InterfaceColor, InterfaceLook } from '@umbraco-cms/backoffice/external/uui';
import { UserStateModel } from '@umbraco-cms/backoffice/backend-api';
@@ -13,7 +13,7 @@ describe('UmbUserExtensions', () => {
];
testCases.forEach((testCase) => {
const { look, color } = getLookAndColorFromUserStatus(testCase.status);
const { look, color } = getDisplayStateFromUserStatus(testCase.status);
expect(look).to.equal(testCase.look);
expect(color).to.equal(testCase.color);
});

View File

@@ -1,18 +1,24 @@
import { InterfaceColor, InterfaceLook } from '@umbraco-cms/backoffice/external/uui';
import { UserStateModel } from '@umbraco-cms/backoffice/backend-api';
export const getLookAndColorFromUserStatus = (
status?: UserStateModel
): { look: InterfaceLook; color: InterfaceColor } => {
switch (status) {
case UserStateModel.INACTIVE:
case UserStateModel.INVITED:
return { look: 'primary', color: 'warning' };
case UserStateModel.ACTIVE:
return { look: 'primary', color: 'positive' };
case UserStateModel.DISABLED:
return { look: 'primary', color: 'danger' };
default:
return { look: 'secondary', color: 'default' };
}
};
interface DisplayStatus {
look: InterfaceLook;
color: InterfaceColor;
key: string;
}
var userStates: DisplayStatus[] = [
{ "key": "All", color:"positive", look: "secondary" } ,
{ "key": "Active", "color": "positive", look: "primary" },
{ "key": "Disabled", "color": "danger", look: "primary" },
{ "key": "LockedOut", "color": "danger", look: "secondary" },
{ "key": "Invited", "color": "warning", look: "primary" },
{ "key": "Inactive", "color": "warning", look: "secondary" }
];
export const getDisplayStateFromUserStatus = (status?: UserStateModel): DisplayStatus =>
userStates
.filter(state => state.key === status)!
.map(state => ({
...state,
key:'state'+state.key
}))[0]