Skip to content
Draft
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
1 change: 1 addition & 0 deletions projects/step-core/src/lib/modules/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export * from './injectables/auth.service';
export * from './injectables/additional-right-rule.service';
export * from './injectables/credentials.service';
export * from './injectables/logout-cleanup.token';
export * from './injectables/users.service';

export * from './pipes/has-right.pipe';

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { User } from '../../../client/step-client-module';

export interface UsersStrategy {
getUserById(userId: string): Observable<User | undefined>;
}

const DEFAULT_USER_STRATEGY: UsersStrategy = {
getUserById(userId: string): Observable<User | undefined> {
return of(undefined);
},
};

@Injectable({
providedIn: 'root',
})
export class UsersService implements UsersStrategy {
private strategy: UsersStrategy = DEFAULT_USER_STRATEGY;

getUserById(userId: string): Observable<User | undefined> {
return this.strategy.getUserById(userId);
}

useStrategy(strategy: UsersStrategy): void {
this.strategy = strategy;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div [matTooltip]="displayTooltip()">{{ initials() }}</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@use 'projects/step-core/styles/core-variables' as var;

:host {
display: flex;
width: 2em;
height: 2em;
border-radius: 2em;
align-items: center;
justify-content: center;
& > div {
user-select: none;
color: var.$white;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { ChangeDetectionStrategy, Component, computed, HostBinding, input, Input } from '@angular/core';

const DELIMITERS = new Set([' ', '_', '-', '.', '+']);
const DEFAULT_BACKGROUND = '#4b5565';

@Component({
selector: 'step-avatar-circle',
templateUrl: './avatar-circle.component.html',
styleUrl: './avatar-circle.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AvatarCircleComponent {
@HostBinding('style.background-color')
@Input({ transform: (value: string | undefined | null) => value || DEFAULT_BACKGROUND })
background = DEFAULT_BACKGROUND;

name = input('', {
transform: (value: string | undefined | null) => value ?? '',
});

tooltip = input<string | undefined | null>(undefined);

protected displayTooltip = computed(() => this.tooltip() ?? this.name());

protected initials = computed(() => {
const name = this.name().trim();
if (!name) {
return '';
}

const words = this.splitStringWithMultipleDelimiters(name);

if (!words.length) {
return '';
}

if (words.length === 1) {
return words[0][0].toUpperCase();
}

return words[0][0].toUpperCase() + words[words.length - 1][0].toUpperCase();
});

private splitStringWithMultipleDelimiters(str: string): string[] {
const parts = [];
let buf = '';

for (let i = 0; i < str.length; i++) {
if (DELIMITERS.has(str[i])) {
if (buf) {
parts.push(buf);
buf = '';
}
continue;
}
buf += str[i];
}
if (buf) {
parts.push(buf);
}
return parts;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { InjectionToken } from '@angular/core';

export const AVATAR_COLOR_PREFERENCE_KEY = new InjectionToken('Avatar color preference key', {
factory: () => 'avatar_color',
});
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { ConfirmationDialogComponent } from './components/confirmation-dialog/co
import { MessagesListDialogComponent } from './components/messages-list-dialog/messages-list-dialog.component';
import { MessageDialogComponent } from './components/message-dialog/message-dialog.component';
import { ProjectNamePipe } from './pipes/project-name.pipe';
import { AvatarCircleComponent } from './components/avatar-circle/avatar-circle.component';

@NgModule({
imports: [CommonModule, FormsModule, ReactiveFormsModule, StepMaterialModule, RouterModule],
Expand Down Expand Up @@ -107,6 +108,7 @@ import { ProjectNamePipe } from './pipes/project-name.pipe';
MarkerComponent,
AlertsContainerComponent,
ProjectNamePipe,
AvatarCircleComponent,
],
exports: [
CommonModule,
Expand Down Expand Up @@ -164,6 +166,7 @@ import { ProjectNamePipe } from './pipes/project-name.pipe';
MarkerComponent,
AlertsContainerComponent,
ProjectNamePipe,
AvatarCircleComponent,
],
})
export class StepBasicsModule {}
Expand Down Expand Up @@ -244,6 +247,7 @@ export * from './injectables/item-hover-receiver.service';
export * from './injectables/item-hold-receiver.service';
export * from './injectables/object-utils.service';
export * from './injectables/dialogs.service';
export * from './injectables/avatar-color-preference-key.token';
export * from './types/bulk-operation-type.enum';
export * from './types/string-array-regex';
export * from './injectables/dialog-parent.service';
Expand All @@ -265,5 +269,6 @@ export * from './components/confirmation-dialog/confirmation-dialog.component';
export * from './components/messages-list-dialog/messages-list-dialog.component';
export * from './components/message-dialog/message-dialog.component';
export * from './components/dialog-route/dialog-route.component';
export * from './components/avatar-circle/avatar-circle.component';
export * from './types/mutable';
export * from './types/date-format.enum';
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@for (color of colors(); track color) {
@if (!color) {
<div class="clear" matTooltip="Clear color" (click)="selectColor('')">
<step-icon name="x" />
</div>
} @else {
<div
class="color-item"
[style.background-color]="color"
[class.selected]="color === selectedColor"
(click)="selectColor(color)"
></div>
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@use 'projects/step-core/styles/core-variables' as var;
@use 'projects/step-core/styles/core-mixins' as core;

:host {
display: grid;
grid-template-columns: repeat(10, 4rem);
grid-auto-rows: 4rem;
gap: 0.1rem;

&:not(.disabled) .color-item {
cursor: pointer;
}
}

.color-item.selected {
border: dotted 0.2rem var.$white;
}

.clear {
border: solid 0.2rem var.$muted;
display: flex;
align-items: center;
justify-content: center;
step-icon {
@include core.step-icon-size(6.8rem);
stroke-width: 0.5px;
color: var.$muted;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { ChangeDetectionStrategy, Component, computed, forwardRef, HostBinding, inject, input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { StepBasicsModule } from '../../../basics/step-basics.module';
import { COLORS } from '../../injectables/colors.token';

type OnChange = (value: string) => void;
type OnTouch = () => void;

@Component({
selector: 'step-color-chooser',
standalone: true,
imports: [StepBasicsModule],
templateUrl: './color-chooser.component.html',
styleUrl: './color-chooser.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ColorChooserComponent),
multi: true,
},
],
})
export class ColorChooserComponent implements ControlValueAccessor {
private _colors = inject(COLORS);

showClearColor = input.required<boolean>();

protected colors = computed(() => (this.showClearColor() ? [...this._colors, ''] : this._colors));

protected selectedColor?: string;

@HostBinding('class.disabled')
protected isDisabled?: boolean;

private onChange?: OnChange;
private onTouch?: OnTouch;

writeValue(value: string): void {
this.selectedColor = value;
}

registerOnChange(fn: OnChange): void {
this.onChange = fn;
}

registerOnTouched(fn: OnTouch): void {
this.onTouch = fn;
}

setDisabledState(isDisabled: boolean): void {
this.isDisabled = isDisabled;
}

protected selectColor(color: string): void {
if (this.isDisabled) {
return;
}
this.selectedColor = color;
this.onChange?.(color);
this.onTouch?.();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ColorField } from '../../types/color-field';
import { Directive, effect, ElementRef, inject, input, Signal } from '@angular/core';
import { ColorPickerComponent } from '../color-picker/color-picker.component';
import { ColorPickerSettings } from '../../types/color-picker-settings';

@Directive({})
export abstract class ColorFieldBase implements ColorField {
protected _elRef = inject<ElementRef<HTMLElement>>(ElementRef);

colorPicker = input<ColorPickerComponent | undefined>();

private effectPickerChanged = effect(() => {
this.colorPicker()?.registerInput(this);
});

getConnectedOverlayOrigin(): ElementRef | undefined {
return this._elRef;
}

abstract getModel(): string | undefined;
abstract setModel(value?: string): void;
abstract isDisabled(): boolean;

chooseColor(settings?: ColorPickerSettings): void {
if (this.isDisabled()) {
return;
}
this.colorPicker()?.open(settings);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<button type="button" mat-stroked-button [disabled]="disabled" (click)="chooseColor()">
<div class="color-container" [style.background-color]="model"></div>
</button>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.color-container {
width: 3.5rem;
height: 2rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { ColorFieldBase } from './color-field.base';
import { ChangeDetectionStrategy, Component, forwardRef, HostListener } from '@angular/core';
import { StepBasicsModule } from '../../../basics/step-basics.module';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

type OnChange = (value?: string) => void;
type OnTouch = () => void;

@Component({
selector: 'step-color-field',
templateUrl: './color-field.component.html',
styleUrl: './color-field.component.scss',
standalone: true,
imports: [StepBasicsModule],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ColorFieldComponent),
multi: true,
},
],
})
export class ColorFieldComponent extends ColorFieldBase implements ControlValueAccessor {
private onChange?: OnChange;
private onTouch?: OnTouch;

protected model?: string;

protected disabled = false;

registerOnChange(fn: OnChange): void {
this.onChange = fn;
}

registerOnTouched(fn: OnTouch): void {
this.onTouch = fn;
}

setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}

writeValue(model?: string): void {
this.model = model;
}

override getModel(): string | undefined {
return this.model;
}

override isDisabled(): boolean {
return this.disabled;
}

override setModel(value?: string): void {
this.model = value;
this.onChange?.(value);
}

@HostListener('blur')
private handleBlur(): void {
this.onTouch?.();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<div cdkTrapFocus role="dialog" [attr.aria-modal]="true">
<mat-card>
@if (settings.title) {
<mat-card-header>
<mat-card-title>{{ settings.title }}</mat-card-title>
</mat-card-header>
}
<mat-card-content>
<step-color-chooser [(ngModel)]="color" [showClearColor]="!!settings.showClearColor" />
</mat-card-content>
</mat-card>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
step-color-picker-content mat-card {
padding: 0 !important;
}
Loading