added routing and user details
This commit is contained in:
@@ -1,219 +1,326 @@
|
||||
import { css, html, LitElement, nothing } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { UmbContextConsumerMixin } from '../../../../../core/context';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import UmbEditorViewUsersElement, { UserItem } from './editor-view-users.element';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { UmbContextConsumerMixin, UmbContextProviderMixin } from '../../../../../core/context';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { InterfaceColor, InterfaceLook } from '@umbraco-ui/uui-base/lib/types';
|
||||
import './editor-view-users-table.element';
|
||||
import './editor-view-users-grid.element';
|
||||
import './editor-view-users-selection.element';
|
||||
import { IRoute } from 'router-slot';
|
||||
|
||||
interface TableColumn {
|
||||
export type UsersViewType = 'list' | 'grid';
|
||||
|
||||
export interface UserItem {
|
||||
key: string;
|
||||
name: string;
|
||||
sort: (items: Array<UserItem>, desc: boolean) => Array<UserItem>;
|
||||
userGroup: string;
|
||||
lastLogin: string;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
@customElement('umb-editor-view-users-list')
|
||||
export class UmbEditorViewUsersListElement extends UmbContextConsumerMixin(LitElement) {
|
||||
export class UmbEditorViewUsersListElement extends UmbContextProviderMixin(LitElement) {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
uui-table {
|
||||
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));
|
||||
#sticky-top {
|
||||
position: sticky;
|
||||
top: -1px;
|
||||
z-index: 1;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0), 0 1px 2px rgba(0, 0, 0, 0);
|
||||
transition: 250ms box-shadow ease-in-out;
|
||||
}
|
||||
uui-table-row uui-checkbox {
|
||||
display: none;
|
||||
|
||||
#sticky-top.header-shadow {
|
||||
box-shadow: var(--uui-shadow-depth-2);
|
||||
}
|
||||
uui-table-row:hover uui-icon,
|
||||
uui-table-row[select-only] uui-icon {
|
||||
display: none;
|
||||
}
|
||||
uui-table-row:hover uui-checkbox,
|
||||
uui-table-row[select-only] uui-checkbox {
|
||||
display: inline-block;
|
||||
}
|
||||
uui-table-head-cell:hover {
|
||||
--uui-symbol-sort-hover: 1;
|
||||
}
|
||||
uui-table-head-cell button {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-weight: inherit;
|
||||
font-size: inherit;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
#user-list-top-bar {
|
||||
padding: var(--uui-size-space-4) var(--uui-size-space-6);
|
||||
background-color: var(--uui-color-surface-alt);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
white-space: nowrap;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
#user-list {
|
||||
padding: var(--uui-size-space-6);
|
||||
padding-top: var(--uui-size-space-2);
|
||||
}
|
||||
#input-search {
|
||||
width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@state()
|
||||
private _columns: Array<TableColumn> = [];
|
||||
private tempData = [
|
||||
{
|
||||
key: 'a9b18a00-58f2-420e-bf60-48d33ab156db',
|
||||
name: 'Cecílie Bryon',
|
||||
userGroup: 'Translators',
|
||||
lastLogin: 'Fri, 23 April 2021',
|
||||
status: 'Invited',
|
||||
},
|
||||
{
|
||||
key: '3179d0b2-eec2-4045-b86a-149e13b93e14',
|
||||
name: 'Kathleen G. Smith',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Tue, 6 June 2021',
|
||||
status: 'Disabled',
|
||||
},
|
||||
{
|
||||
key: '1b1c9733-b845-4d9a-9ed2-b2f46c05fd72',
|
||||
name: 'Adrian Andresen',
|
||||
userGroup: 'Administrators',
|
||||
lastLogin: 'Mon, 15 November 2021',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-9330-b66c336d0207',
|
||||
name: 'Lorenza Trentino',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Fri, 13 April 2022',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-9330-b66c336d0202',
|
||||
name: 'John Doe',
|
||||
userGroup: 'Translators',
|
||||
lastLogin: 'Tue, 11 December 2021',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-9330-b66c336d0203',
|
||||
name: 'Jane Doe',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Fri, 13 April 2022',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-9330-b66c336d0204',
|
||||
name: 'John Smith',
|
||||
userGroup: 'Administrators',
|
||||
lastLogin: 'Mon, 15 November 2021',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-9330-b66c336d0205',
|
||||
name: 'Jane Smith',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Fri, 13 April 2022',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-9330-b66c336d0206',
|
||||
name: 'Oliver Twist',
|
||||
userGroup: 'Translators',
|
||||
lastLogin: 'Tue, 11 December 2021',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-2330-b66c336d0207',
|
||||
name: 'Olivia Doe',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Fri, 13 April 2022',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-9330-b66c336d0208',
|
||||
name: 'Hans Hansen',
|
||||
userGroup: 'Administrators',
|
||||
lastLogin: 'Mon, 15 November 2021',
|
||||
},
|
||||
{
|
||||
key: 'a9b18a00-58f2-sjh2-bf60-48d33ab156db',
|
||||
name: 'Brian Adams',
|
||||
userGroup: 'Translators',
|
||||
lastLogin: 'Fri, 23 April 2021',
|
||||
status: 'Invited',
|
||||
},
|
||||
{
|
||||
key: '3179d0b2-eec2-4432-b86a-149e13b93e14',
|
||||
name: 'Smith John',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Tue, 6 June 2021',
|
||||
status: 'Disabled',
|
||||
},
|
||||
{
|
||||
key: '1b1c9723-b845-4d9a-9ed2-b2f46c05fd72',
|
||||
name: 'Reese Witherspoon',
|
||||
userGroup: 'Administrators',
|
||||
lastLogin: 'Mon, 15 November 2021',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-2f94-4e65-9330-b66c336d0207',
|
||||
name: 'Denzel Washington',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Fri, 13 April 2022',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e23-9330-b66c336d0202',
|
||||
name: 'Leonardo DiCaprio',
|
||||
userGroup: 'Translators',
|
||||
lastLogin: 'Tue, 11 December 2021',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-2394-4e65-9330-b66c336d0203',
|
||||
name: 'Idris Elba',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Fri, 13 April 2022',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-9330-b6u7336d0204',
|
||||
name: 'Quentin Tarantino',
|
||||
userGroup: 'Administrators',
|
||||
lastLogin: 'Mon, 15 November 2021',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-2330-c66c336d0205',
|
||||
name: 'Tom Hanks',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Fri, 13 April 2022',
|
||||
},
|
||||
{
|
||||
key: 'b75af82a-b994-4b65-9330-b66c336d0206',
|
||||
name: 'Oprah Winfrey',
|
||||
userGroup: 'Translators',
|
||||
lastLogin: 'Tue, 11 December 2021',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-2s30-b66b336d0207',
|
||||
name: 'Pamela Anderson',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Fri, 13 April 2022',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-9930-b66c336d0l33t',
|
||||
name: 'Keanu Reeves',
|
||||
userGroup: 'Administrators',
|
||||
lastLogin: 'Mon, 15 November 2021',
|
||||
},
|
||||
];
|
||||
|
||||
@state()
|
||||
private _selectionMode = false;
|
||||
private _viewType: UsersViewType = 'grid';
|
||||
|
||||
@state()
|
||||
private _selection: Array<string> = [];
|
||||
private _users: BehaviorSubject<Array<UserItem>> = new BehaviorSubject(this.tempData);
|
||||
public readonly users: Observable<Array<UserItem>> = this._users.asObservable();
|
||||
|
||||
@state()
|
||||
private _sortingColumn: any = '';
|
||||
private _selection: BehaviorSubject<Array<string>> = new BehaviorSubject(<Array<string>>[]);
|
||||
public readonly selection: Observable<Array<string>> = this._selection.asObservable();
|
||||
|
||||
@state()
|
||||
private _sortingDesc = false;
|
||||
|
||||
@state()
|
||||
private _users: Array<UserItem> = [];
|
||||
|
||||
protected _usersContext?: UmbEditorViewUsersElement;
|
||||
protected _usersSubscription?: Subscription;
|
||||
protected _selectionSubscription?: Subscription;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
this.consumeContext('umbUsersContext', (usersContext: UmbEditorViewUsersElement) => {
|
||||
this._usersContext = usersContext;
|
||||
|
||||
this._usersSubscription?.unsubscribe();
|
||||
this._selectionSubscription?.unsubscribe();
|
||||
this._usersSubscription = this._usersContext?.users.subscribe((users: Array<UserItem>) => {
|
||||
this._users = users;
|
||||
});
|
||||
this._selectionSubscription = this._usersContext?.selection.subscribe((selection: Array<string>) => {
|
||||
this._selection = selection;
|
||||
});
|
||||
});
|
||||
|
||||
this._columns = [
|
||||
{
|
||||
name: 'Name',
|
||||
sort: (items: Array<UserItem>, desc: boolean) => {
|
||||
return desc
|
||||
? [...items].sort((a, b) => b.name.localeCompare(a.name))
|
||||
: [...items].sort((a, b) => a.name.localeCompare(b.name));
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'User group',
|
||||
sort: (items: Array<UserItem>, desc: boolean) => {
|
||||
return desc
|
||||
? [...items].sort((a, b) => b.name.localeCompare(a.name))
|
||||
: [...items].sort((a, b) => a.name.localeCompare(b.name));
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Last login',
|
||||
sort: (items: Array<UserItem>, desc: boolean) => {
|
||||
return desc
|
||||
? [...items].sort((a, b) => +new Date(b.lastLogin) - +new Date(a.lastLogin))
|
||||
: [...items].sort((a, b) => +new Date(a.lastLogin) - +new Date(b.lastLogin));
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
sort: (items: Array<UserItem>, desc: boolean) => {
|
||||
return desc
|
||||
? [...items].sort((a, b) =>
|
||||
b.status && a.status ? b.status.localeCompare(a.status) : (a.status ? 1 : 0) - (b.status ? 1 : 0)
|
||||
)
|
||||
: [...items].sort((a, b) =>
|
||||
a.status && b.status ? a.status.localeCompare(b.status) : (b.status ? 1 : 0) - (a.status ? 1 : 0)
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
|
||||
this._usersSubscription?.unsubscribe();
|
||||
this._selectionSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
private _selectAllHandler(event: Event) {
|
||||
console.log('SELECT ALL NOT IMPLEMENTED');
|
||||
}
|
||||
|
||||
private _selectRowHandler(user: UserItem) {
|
||||
this._usersContext?.select(user.key);
|
||||
}
|
||||
|
||||
private _deselectRowHandler(user: UserItem) {
|
||||
this._usersContext?.deselect(user.key);
|
||||
}
|
||||
|
||||
private _sortingHandler(column: TableColumn) {
|
||||
this._sortingDesc = this._sortingColumn === column.name ? !this._sortingDesc : false;
|
||||
this._sortingColumn = column.name;
|
||||
this._users = column.sort(this._users, this._sortingDesc);
|
||||
}
|
||||
|
||||
private _isSelected(key: string) {
|
||||
return this._selection.includes(key);
|
||||
}
|
||||
|
||||
renderHeaderCellTemplate(column: TableColumn) {
|
||||
return html`
|
||||
<uui-table-head-cell style="--uui-table-cell-padding: 0">
|
||||
<button style="padding: var(--uui-size-4) var(--uui-size-5);" @click="${() => this._sortingHandler(column)}">
|
||||
${column.name}
|
||||
<uui-symbol-sort ?active=${this._sortingColumn === column.name} ?descending=${this._sortingDesc}>
|
||||
</uui-symbol-sort>
|
||||
</button>
|
||||
</uui-table-head-cell>
|
||||
`;
|
||||
}
|
||||
|
||||
protected renderRowTemplate = (user: UserItem) => {
|
||||
if (!this._usersContext) return;
|
||||
|
||||
const statusLook = this._usersContext.getTagLookAndColor(user.status ? user.status : '');
|
||||
|
||||
return html`<uui-table-row
|
||||
selectable
|
||||
?select-only=${this._selectionMode}
|
||||
?selected=${this._isSelected(user.key)}
|
||||
@selected=${() => this._usersContext?.select(user.key)}
|
||||
@unselected=${() => this._usersContext?.deselect(user.key)}>
|
||||
<uui-table-cell>
|
||||
<div style="display: flex; align-items: center;">
|
||||
<uui-avatar name="${user.name}"></uui-avatar>
|
||||
</div>
|
||||
</uui-table-cell>
|
||||
<uui-table-cell>
|
||||
<div style="display: flex; align-items: center;">
|
||||
<a style="font-weight: bold;" href="http://">${user.name}</a>
|
||||
</div>
|
||||
</uui-table-cell>
|
||||
<uui-table-cell> ${user.userGroup} </uui-table-cell>
|
||||
<uui-table-cell>${user.lastLogin}</uui-table-cell>
|
||||
<uui-table-cell>
|
||||
${user.status
|
||||
? html`<uui-tag size="s" look="${statusLook.look}" color="${statusLook.color}"> ${user.status} </uui-tag>`
|
||||
: nothing}
|
||||
</uui-table-cell>
|
||||
</uui-table-row>`;
|
||||
private _IntersectionObserverOptions = {
|
||||
root: this,
|
||||
rootMargin: '0px',
|
||||
threshold: 1.0,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.provideContext('umbUsersContext', this);
|
||||
this.setupHeaderIntersectionObserver();
|
||||
}
|
||||
|
||||
public setupHeaderIntersectionObserver() {
|
||||
requestAnimationFrame(() => {
|
||||
const el = this.shadowRoot?.querySelector('#sticky-top');
|
||||
|
||||
if (el) {
|
||||
const options = { threshold: [1] };
|
||||
const callback = (entries: IntersectionObserverEntry[]) =>
|
||||
entries[0].target.classList.toggle('header-shadow', entries[0].intersectionRatio < 1);
|
||||
const observer = new IntersectionObserver(callback, options);
|
||||
|
||||
observer.observe(el);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public setSelection(value: Array<string>) {
|
||||
if (!value) return;
|
||||
this._selection.next(value);
|
||||
this.requestUpdate('selection');
|
||||
}
|
||||
|
||||
public select(key: string) {
|
||||
const selection = this._selection.getValue();
|
||||
this._selection.next([...selection, key]);
|
||||
this.requestUpdate('selection');
|
||||
}
|
||||
|
||||
public deselect(key: string) {
|
||||
const selection = this._selection.getValue();
|
||||
this._selection.next(selection.filter((k) => k !== key));
|
||||
this.requestUpdate('selection');
|
||||
}
|
||||
|
||||
public getTagLookAndColor(status: string): { color: InterfaceColor; look: InterfaceLook } {
|
||||
switch (status.toLowerCase()) {
|
||||
case 'invited':
|
||||
case 'inactive':
|
||||
return { look: 'primary', color: 'warning' };
|
||||
case 'active':
|
||||
return { look: 'primary', color: 'positive' };
|
||||
case 'disabled':
|
||||
return { look: 'primary', color: 'danger' };
|
||||
default:
|
||||
return { look: 'secondary', color: 'default' };
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleViewType() {
|
||||
this._viewType = this._viewType === 'list' ? 'grid' : 'list';
|
||||
}
|
||||
|
||||
private _renderViewType() {
|
||||
switch (this._viewType) {
|
||||
case 'list':
|
||||
return html`<umb-editor-view-users-table></umb-editor-view-users-table>`;
|
||||
case 'grid':
|
||||
return html`<umb-editor-view-users-grid></umb-editor-view-users-grid>`;
|
||||
default:
|
||||
return html`<umb-editor-view-users-grid></umb-editor-view-users-grid>`;
|
||||
}
|
||||
}
|
||||
|
||||
private _renderSelection() {
|
||||
if (this._selection.getValue().length === 0) return nothing;
|
||||
|
||||
return html`<umb-editor-view-users-selection></umb-editor-view-users-selection>`;
|
||||
}
|
||||
|
||||
@state()
|
||||
private _routes: IRoute[] = [
|
||||
{
|
||||
path: 'list',
|
||||
component: () => import('./editor-view-users-table.element'),
|
||||
},
|
||||
{
|
||||
path: 'details/:key',
|
||||
component: () => import('./editor-view-users-user-details.element'),
|
||||
},
|
||||
];
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-table class="uui-text">
|
||||
<uui-table-column style="width: 60px;"></uui-table-column>
|
||||
<uui-table-head>
|
||||
<uui-table-head-cell style="--uui-table-cell-padding: 0">
|
||||
<uui-checkbox
|
||||
style="padding: var(--uui-size-4) var(--uui-size-5);"
|
||||
@change="${this._selectAllHandler}"
|
||||
?checked="${this._selection.length === this._users.length}"></uui-checkbox>
|
||||
</uui-table-head-cell>
|
||||
${this._columns.map((column) => this.renderHeaderCellTemplate(column))}
|
||||
</uui-table-head>
|
||||
${repeat(this._users, (item) => item.key, this.renderRowTemplate)}
|
||||
</uui-table>
|
||||
<div id="sticky-top">
|
||||
<div id="user-list-top-bar">
|
||||
<uui-button label="Invite user" look="outline"></uui-button>
|
||||
<uui-input id="input-search"></uui-input>
|
||||
<div>
|
||||
<uui-button> Status: <b>All</b> </uui-button>
|
||||
<uui-button> Groups: <b>All</b> </uui-button>
|
||||
<uui-button> Order by: <b>Name (A-Z)</b> </uui-button>
|
||||
<uui-button
|
||||
@click=${this._toggleViewType}
|
||||
look="${this._viewType === 'grid' ? 'outline' : 'primary'}"
|
||||
compact>
|
||||
<uui-icon name="settings"></uui-icon>
|
||||
</uui-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${this._renderSelection()}
|
||||
</div>
|
||||
|
||||
<router-slot .routes=${this._routes}></router-slot>
|
||||
|
||||
<div id="user-list">${this._renderViewType()}</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { UmbContextConsumerMixin } from '../../../../../core/context';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import UmbEditorViewUsersElement, { UserItem } from './editor-view-users.element';
|
||||
import { Subscription } from 'rxjs';
|
||||
import UmbEditorViewUsersListElement, { UserItem } from './editor-view-users-list.element';
|
||||
|
||||
@customElement('umb-editor-view-users-selection')
|
||||
export class UmbEditorViewUsersSelectionElement extends UmbContextConsumerMixin(LitElement) {
|
||||
@@ -30,14 +30,14 @@ export class UmbEditorViewUsersSelectionElement extends UmbContextConsumerMixin(
|
||||
@state()
|
||||
private _selection: Array<string> = [];
|
||||
|
||||
protected _usersContext?: UmbEditorViewUsersElement;
|
||||
protected _usersContext?: UmbEditorViewUsersListElement;
|
||||
protected _usersSubscription?: Subscription;
|
||||
protected _selectionSubscription?: Subscription;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
this.consumeContext('umbUsersContext', (usersContext: UmbEditorViewUsersElement) => {
|
||||
this.consumeContext('umbUsersContext', (usersContext: UmbEditorViewUsersListElement) => {
|
||||
this._usersContext = usersContext;
|
||||
|
||||
this._usersSubscription?.unsubscribe();
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
import { css, html, LitElement, nothing } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { UmbContextConsumerMixin } from '../../../../../core/context';
|
||||
import { repeat } from 'lit/directives/repeat.js';
|
||||
import { Subscription } from 'rxjs';
|
||||
import UmbEditorViewUsersListElement, { UserItem } from './editor-view-users-list.element';
|
||||
|
||||
interface TableColumn {
|
||||
name: string;
|
||||
sort: (items: Array<UserItem>, desc: boolean) => Array<UserItem>;
|
||||
}
|
||||
|
||||
@customElement('umb-editor-view-users-table')
|
||||
export class UmbEditorViewUsersTableElement extends UmbContextConsumerMixin(LitElement) {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
uui-table {
|
||||
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));
|
||||
}
|
||||
uui-table-row uui-checkbox {
|
||||
display: none;
|
||||
}
|
||||
uui-table-row:hover uui-icon,
|
||||
uui-table-row[select-only] uui-icon {
|
||||
display: none;
|
||||
}
|
||||
uui-table-row:hover uui-checkbox,
|
||||
uui-table-row[select-only] uui-checkbox {
|
||||
display: inline-block;
|
||||
}
|
||||
uui-table-head-cell:hover {
|
||||
--uui-symbol-sort-hover: 1;
|
||||
}
|
||||
uui-table-head-cell button {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-weight: inherit;
|
||||
font-size: inherit;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@state()
|
||||
private _columns: Array<TableColumn> = [];
|
||||
|
||||
@state()
|
||||
private _selectionMode = false;
|
||||
|
||||
@state()
|
||||
private _selection: Array<string> = [];
|
||||
|
||||
@state()
|
||||
private _sortingColumn: any = '';
|
||||
|
||||
@state()
|
||||
private _sortingDesc = false;
|
||||
|
||||
@state()
|
||||
private _users: Array<UserItem> = [];
|
||||
|
||||
protected _usersContext?: UmbEditorViewUsersListElement;
|
||||
protected _usersSubscription?: Subscription;
|
||||
protected _selectionSubscription?: Subscription;
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
|
||||
this.consumeContext('umbUsersContext', (usersContext: UmbEditorViewUsersListElement) => {
|
||||
this._usersContext = usersContext;
|
||||
|
||||
this._usersSubscription?.unsubscribe();
|
||||
this._selectionSubscription?.unsubscribe();
|
||||
this._usersSubscription = this._usersContext?.users.subscribe((users: Array<UserItem>) => {
|
||||
this._users = users;
|
||||
});
|
||||
this._selectionSubscription = this._usersContext?.selection.subscribe((selection: Array<string>) => {
|
||||
this._selection = selection;
|
||||
});
|
||||
});
|
||||
|
||||
this._columns = [
|
||||
{
|
||||
name: 'Name',
|
||||
sort: (items: Array<UserItem>, desc: boolean) => {
|
||||
return desc
|
||||
? [...items].sort((a, b) => b.name.localeCompare(a.name))
|
||||
: [...items].sort((a, b) => a.name.localeCompare(b.name));
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'User group',
|
||||
sort: (items: Array<UserItem>, desc: boolean) => {
|
||||
return desc
|
||||
? [...items].sort((a, b) => b.name.localeCompare(a.name))
|
||||
: [...items].sort((a, b) => a.name.localeCompare(b.name));
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Last login',
|
||||
sort: (items: Array<UserItem>, desc: boolean) => {
|
||||
return desc
|
||||
? [...items].sort((a, b) => +new Date(b.lastLogin) - +new Date(a.lastLogin))
|
||||
: [...items].sort((a, b) => +new Date(a.lastLogin) - +new Date(b.lastLogin));
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
sort: (items: Array<UserItem>, desc: boolean) => {
|
||||
return desc
|
||||
? [...items].sort((a, b) =>
|
||||
b.status && a.status ? b.status.localeCompare(a.status) : (a.status ? 1 : 0) - (b.status ? 1 : 0)
|
||||
)
|
||||
: [...items].sort((a, b) =>
|
||||
a.status && b.status ? a.status.localeCompare(b.status) : (b.status ? 1 : 0) - (a.status ? 1 : 0)
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
|
||||
this._usersSubscription?.unsubscribe();
|
||||
this._selectionSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
private _selectAllHandler(event: Event) {
|
||||
console.log('SELECT ALL NOT IMPLEMENTED');
|
||||
}
|
||||
|
||||
private _selectRowHandler(user: UserItem) {
|
||||
this._usersContext?.select(user.key);
|
||||
}
|
||||
|
||||
private _deselectRowHandler(user: UserItem) {
|
||||
this._usersContext?.deselect(user.key);
|
||||
}
|
||||
|
||||
private _sortingHandler(column: TableColumn) {
|
||||
this._sortingDesc = this._sortingColumn === column.name ? !this._sortingDesc : false;
|
||||
this._sortingColumn = column.name;
|
||||
this._users = column.sort(this._users, this._sortingDesc);
|
||||
}
|
||||
|
||||
private _isSelected(key: string) {
|
||||
return this._selection.includes(key);
|
||||
}
|
||||
|
||||
renderHeaderCellTemplate(column: TableColumn) {
|
||||
return html`
|
||||
<uui-table-head-cell style="--uui-table-cell-padding: 0">
|
||||
<button style="padding: var(--uui-size-4) var(--uui-size-5);" @click="${() => this._sortingHandler(column)}">
|
||||
${column.name}
|
||||
<uui-symbol-sort ?active=${this._sortingColumn === column.name} ?descending=${this._sortingDesc}>
|
||||
</uui-symbol-sort>
|
||||
</button>
|
||||
</uui-table-head-cell>
|
||||
`;
|
||||
}
|
||||
|
||||
protected renderRowTemplate = (user: UserItem) => {
|
||||
if (!this._usersContext) return;
|
||||
|
||||
const statusLook = this._usersContext.getTagLookAndColor(user.status ? user.status : '');
|
||||
|
||||
return html`<uui-table-row
|
||||
selectable
|
||||
?select-only=${this._selectionMode}
|
||||
?selected=${this._isSelected(user.key)}
|
||||
@selected=${() => this._usersContext?.select(user.key)}
|
||||
@unselected=${() => this._usersContext?.deselect(user.key)}>
|
||||
<uui-table-cell>
|
||||
<div style="display: flex; align-items: center;">
|
||||
<uui-avatar name="${user.name}"></uui-avatar>
|
||||
</div>
|
||||
</uui-table-cell>
|
||||
<uui-table-cell>
|
||||
<div style="display: flex; align-items: center;">
|
||||
<a style="font-weight: bold;" href="http://">${user.name}</a>
|
||||
</div>
|
||||
</uui-table-cell>
|
||||
<uui-table-cell> ${user.userGroup} </uui-table-cell>
|
||||
<uui-table-cell>${user.lastLogin}</uui-table-cell>
|
||||
<uui-table-cell>
|
||||
${user.status
|
||||
? html`<uui-tag size="s" look="${statusLook.look}" color="${statusLook.color}"> ${user.status} </uui-tag>`
|
||||
: nothing}
|
||||
</uui-table-cell>
|
||||
</uui-table-row>`;
|
||||
};
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-table class="uui-text">
|
||||
<uui-table-column style="width: 60px;"></uui-table-column>
|
||||
<uui-table-head>
|
||||
<uui-table-head-cell style="--uui-table-cell-padding: 0">
|
||||
<uui-checkbox
|
||||
style="padding: var(--uui-size-4) var(--uui-size-5);"
|
||||
@change="${this._selectAllHandler}"
|
||||
?checked="${this._selection.length === this._users.length}"></uui-checkbox>
|
||||
</uui-table-head-cell>
|
||||
${this._columns.map((column) => this.renderHeaderCellTemplate(column))}
|
||||
</uui-table-head>
|
||||
${repeat(this._users, (item) => item.key, this.renderRowTemplate)}
|
||||
</uui-table>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbEditorViewUsersTableElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-editor-view-users-table': UmbEditorViewUsersTableElement;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { css, html, LitElement, nothing } from 'lit';
|
||||
import { UUITextStyles } from '@umbraco-ui/uui-css/lib';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { UmbContextConsumerMixin } from '../../../../../core/context';
|
||||
|
||||
@customElement('umb-editor-view-users-user-details')
|
||||
export class UmbEditorViewUsersUserDetailsElement extends UmbContextConsumerMixin(LitElement) {
|
||||
static styles = [UUITextStyles, css``];
|
||||
|
||||
render() {
|
||||
return html`USER DETAILS `;
|
||||
}
|
||||
}
|
||||
|
||||
export default UmbEditorViewUsersUserDetailsElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'umb-editor-view-users-user-details': UmbEditorViewUsersUserDetailsElement;
|
||||
}
|
||||
}
|
||||
@@ -4,309 +4,30 @@ import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { UmbContextConsumerMixin, UmbContextProviderMixin } from '../../../../../core/context';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { InterfaceColor, InterfaceLook } from '@umbraco-ui/uui-base/lib/types';
|
||||
import './editor-view-users-list.element';
|
||||
import './editor-view-users-table.element';
|
||||
import './editor-view-users-grid.element';
|
||||
import './editor-view-users-selection.element';
|
||||
|
||||
export type UsersViewType = 'list' | 'grid';
|
||||
|
||||
export interface UserItem {
|
||||
key: string;
|
||||
name: string;
|
||||
userGroup: string;
|
||||
lastLogin: string;
|
||||
status?: string;
|
||||
}
|
||||
import './editor-view-users-user-details.element';
|
||||
import { IRoute } from 'router-slot';
|
||||
|
||||
@customElement('umb-editor-view-users')
|
||||
export class UmbEditorViewUsersElement extends UmbContextProviderMixin(LitElement) {
|
||||
static styles = [
|
||||
UUITextStyles,
|
||||
css`
|
||||
#sticky-top {
|
||||
position: sticky;
|
||||
top: -1px;
|
||||
z-index: 1;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0), 0 1px 2px rgba(0, 0, 0, 0);
|
||||
transition: 250ms box-shadow ease-in-out;
|
||||
}
|
||||
|
||||
#sticky-top.header-shadow {
|
||||
box-shadow: var(--uui-shadow-depth-2);
|
||||
}
|
||||
|
||||
#user-list-top-bar {
|
||||
padding: var(--uui-size-space-4) var(--uui-size-space-6);
|
||||
background-color: var(--uui-color-surface-alt);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
white-space: nowrap;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
#user-list {
|
||||
padding: var(--uui-size-space-6);
|
||||
}
|
||||
#input-search {
|
||||
width: 100%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
private tempData = [
|
||||
{
|
||||
key: 'a9b18a00-58f2-420e-bf60-48d33ab156db',
|
||||
name: 'Cecílie Bryon',
|
||||
userGroup: 'Translators',
|
||||
lastLogin: 'Fri, 23 April 2021',
|
||||
status: 'Invited',
|
||||
},
|
||||
{
|
||||
key: '3179d0b2-eec2-4045-b86a-149e13b93e14',
|
||||
name: 'Kathleen G. Smith',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Tue, 6 June 2021',
|
||||
status: 'Disabled',
|
||||
},
|
||||
{
|
||||
key: '1b1c9733-b845-4d9a-9ed2-b2f46c05fd72',
|
||||
name: 'Adrian Andresen',
|
||||
userGroup: 'Administrators',
|
||||
lastLogin: 'Mon, 15 November 2021',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-9330-b66c336d0207',
|
||||
name: 'Lorenza Trentino',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Fri, 13 April 2022',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-9330-b66c336d0202',
|
||||
name: 'John Doe',
|
||||
userGroup: 'Translators',
|
||||
lastLogin: 'Tue, 11 December 2021',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-9330-b66c336d0203',
|
||||
name: 'Jane Doe',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Fri, 13 April 2022',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-9330-b66c336d0204',
|
||||
name: 'John Smith',
|
||||
userGroup: 'Administrators',
|
||||
lastLogin: 'Mon, 15 November 2021',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-9330-b66c336d0205',
|
||||
name: 'Jane Smith',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Fri, 13 April 2022',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-9330-b66c336d0206',
|
||||
name: 'Oliver Twist',
|
||||
userGroup: 'Translators',
|
||||
lastLogin: 'Tue, 11 December 2021',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-2330-b66c336d0207',
|
||||
name: 'Olivia Doe',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Fri, 13 April 2022',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-9330-b66c336d0208',
|
||||
name: 'Hans Hansen',
|
||||
userGroup: 'Administrators',
|
||||
lastLogin: 'Mon, 15 November 2021',
|
||||
},
|
||||
{
|
||||
key: 'a9b18a00-58f2-sjh2-bf60-48d33ab156db',
|
||||
name: 'Brian Adams',
|
||||
userGroup: 'Translators',
|
||||
lastLogin: 'Fri, 23 April 2021',
|
||||
status: 'Invited',
|
||||
},
|
||||
{
|
||||
key: '3179d0b2-eec2-4432-b86a-149e13b93e14',
|
||||
name: 'Smith John',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Tue, 6 June 2021',
|
||||
status: 'Disabled',
|
||||
},
|
||||
{
|
||||
key: '1b1c9723-b845-4d9a-9ed2-b2f46c05fd72',
|
||||
name: 'Reese Witherspoon',
|
||||
userGroup: 'Administrators',
|
||||
lastLogin: 'Mon, 15 November 2021',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-2f94-4e65-9330-b66c336d0207',
|
||||
name: 'Denzel Washington',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Fri, 13 April 2022',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e23-9330-b66c336d0202',
|
||||
name: 'Leonardo DiCaprio',
|
||||
userGroup: 'Translators',
|
||||
lastLogin: 'Tue, 11 December 2021',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-2394-4e65-9330-b66c336d0203',
|
||||
name: 'Idris Elba',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Fri, 13 April 2022',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-9330-b6u7336d0204',
|
||||
name: 'Quentin Tarantino',
|
||||
userGroup: 'Administrators',
|
||||
lastLogin: 'Mon, 15 November 2021',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-2330-c66c336d0205',
|
||||
name: 'Tom Hanks',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Fri, 13 April 2022',
|
||||
},
|
||||
{
|
||||
key: 'b75af82a-b994-4b65-9330-b66c336d0206',
|
||||
name: 'Oprah Winfrey',
|
||||
userGroup: 'Translators',
|
||||
lastLogin: 'Tue, 11 December 2021',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-2s30-b66b336d0207',
|
||||
name: 'Pamela Anderson',
|
||||
userGroup: 'Editors',
|
||||
lastLogin: 'Fri, 13 April 2022',
|
||||
},
|
||||
{
|
||||
key: 'b75af81a-b994-4e65-9930-b66c336d0l33t',
|
||||
name: 'Keanu Reeves',
|
||||
userGroup: 'Administrators',
|
||||
lastLogin: 'Mon, 15 November 2021',
|
||||
},
|
||||
];
|
||||
static styles = [UUITextStyles, css``];
|
||||
|
||||
@state()
|
||||
private _viewType: UsersViewType = 'grid';
|
||||
|
||||
private _users: BehaviorSubject<Array<UserItem>> = new BehaviorSubject(this.tempData);
|
||||
public readonly users: Observable<Array<UserItem>> = this._users.asObservable();
|
||||
|
||||
private _selection: BehaviorSubject<Array<string>> = new BehaviorSubject(<Array<string>>[]);
|
||||
public readonly selection: Observable<Array<string>> = this._selection.asObservable();
|
||||
|
||||
private _IntersectionObserverOptions = {
|
||||
root: this,
|
||||
rootMargin: '0px',
|
||||
threshold: 1.0,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.provideContext('umbUsersContext', this);
|
||||
this.setupHeaderIntersectionObserver();
|
||||
}
|
||||
|
||||
public setupHeaderIntersectionObserver() {
|
||||
requestAnimationFrame(() => {
|
||||
const el = this.shadowRoot?.querySelector('#sticky-top');
|
||||
|
||||
if (el) {
|
||||
const options = { threshold: [1] };
|
||||
const callback = (entries: IntersectionObserverEntry[]) =>
|
||||
entries[0].target.classList.toggle('header-shadow', entries[0].intersectionRatio < 1);
|
||||
const observer = new IntersectionObserver(callback, options);
|
||||
|
||||
observer.observe(el);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public setSelection(value: Array<string>) {
|
||||
if (!value) return;
|
||||
this._selection.next(value);
|
||||
this.requestUpdate('selection');
|
||||
}
|
||||
|
||||
public select(key: string) {
|
||||
const selection = this._selection.getValue();
|
||||
this._selection.next([...selection, key]);
|
||||
this.requestUpdate('selection');
|
||||
}
|
||||
|
||||
public deselect(key: string) {
|
||||
const selection = this._selection.getValue();
|
||||
this._selection.next(selection.filter((k) => k !== key));
|
||||
this.requestUpdate('selection');
|
||||
}
|
||||
|
||||
public getTagLookAndColor(status: string): { color: InterfaceColor; look: InterfaceLook } {
|
||||
switch (status.toLowerCase()) {
|
||||
case 'invited':
|
||||
case 'inactive':
|
||||
return { look: 'primary', color: 'warning' };
|
||||
case 'active':
|
||||
return { look: 'primary', color: 'positive' };
|
||||
case 'disabled':
|
||||
return { look: 'primary', color: 'danger' };
|
||||
default:
|
||||
return { look: 'secondary', color: 'default' };
|
||||
}
|
||||
}
|
||||
|
||||
private _toggleViewType() {
|
||||
this._viewType = this._viewType === 'list' ? 'grid' : 'list';
|
||||
}
|
||||
|
||||
private _renderViewType() {
|
||||
switch (this._viewType) {
|
||||
case 'list':
|
||||
return html`<umb-editor-view-users-list></umb-editor-view-users-list>`;
|
||||
case 'grid':
|
||||
return html`<umb-editor-view-users-grid></umb-editor-view-users-grid>`;
|
||||
default:
|
||||
return html`<umb-editor-view-users-grid></umb-editor-view-users-grid>`;
|
||||
}
|
||||
}
|
||||
|
||||
private _renderSelection() {
|
||||
if (this._selection.getValue().length === 0) return nothing;
|
||||
|
||||
return html`<umb-editor-view-users-selection></umb-editor-view-users-selection>`;
|
||||
}
|
||||
private _routes: IRoute[] = [
|
||||
{
|
||||
path: 'list',
|
||||
component: () => import('./editor-view-users-list.element'),
|
||||
},
|
||||
{
|
||||
path: 'details/:key',
|
||||
component: () => import('./editor-view-users-user-details.element'),
|
||||
},
|
||||
];
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div id="sticky-top">
|
||||
<div id="user-list-top-bar">
|
||||
<uui-button label="Invite user" look="outline"></uui-button>
|
||||
<uui-input id="input-search"></uui-input>
|
||||
<div>
|
||||
<uui-button> Status: <b>All</b> </uui-button>
|
||||
<uui-button> Groups: <b>All</b> </uui-button>
|
||||
<uui-button> Order by: <b>Name (A-Z)</b> </uui-button>
|
||||
<uui-button
|
||||
@click=${this._toggleViewType}
|
||||
look="${this._viewType === 'grid' ? 'outline' : 'primary'}"
|
||||
compact>
|
||||
<uui-icon name="settings"></uui-icon>
|
||||
</uui-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${this._renderSelection()}
|
||||
</div>
|
||||
<!-- <div id="sticky-top-shadow"></div> -->
|
||||
|
||||
<div id="user-list">${this._renderViewType()}</div>
|
||||
`;
|
||||
return html` <router-slot .routes=${this._routes}></router-slot> `;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user