Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Floating button for admin page #3138

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@
<vdr-main-nav displayMode="settings" (itemClick)="collapseNav()"></vdr-main-nav>
</div>
<div class="mx-2 flex center mb-1" [class.mt-2]="hideVersion && !devMode">
<div *ngIf="!hideVersion" class="version">
v{{ version }}
</div>
<div *ngIf="!hideVersion" class="version">v{{ version }}</div>
<vdr-dropdown *ngIf="devMode">
<button class="icon-button dev-mode-button" vdrDropdownTrigger title="DEV MODE">
<clr-icon shape="code" size="24"></clr-icon> DEV MODE
Expand Down Expand Up @@ -79,4 +77,5 @@
<div class="content-area"><router-outlet></router-outlet></div>
</div>
</div>
<vdr-floating-button></vdr-floating-button>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<div class="floating-button-container">
<button
*ngFor="let button of floatingButtons"
class="floating-button"
[ngClass]="{ disabled: button.disabled }"
[style.backgroundColor]="button.backgroundColor"
(click)="handleClick(button)"
[style.width]="button.buttonSize ? button.buttonSize + 'px' : '60px'"
[style.height]="button.buttonSize ? button.buttonSize + 'px' : '60px'"
[style.display]="
button.visible &&
(button.requiresPermission == null ||
button.requiresPermission === false ||
(button.requiresPermission && button.requiresPermission | hasPermission))
? 'flex'
: 'none'
"
>
<clr-icon
[attr.shape]="button.icon"
[attr.size]="button.iconSize || '16'"
[style.color]="button.iconColor"
></clr-icon>
<span>{{ button.label }}</span>
</button>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.floating-button-container {
position: fixed;
bottom: 20px;
right: 20px;
display: flex;
flex-direction: row;
gap: 10px;
z-index: 1000;
}

.floating-button {
position: relative;
bottom: auto;
right: auto;
background-color: #2e83b2;
color: white;
border: none;
border-radius: 50%;
width: 60px;
height: 60px;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
cursor: pointer;
z-index: 1000;
}

.floating-button:hover {
background-color: #1e5e7f;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription, take } from 'rxjs';
import { NavigationEnd, Router } from '@angular/router';
import { FloatingButton } from '../../providers/floating-button/floating-button-types';
import { FloatingButtonService } from '../../providers/floating-button/floating-button.service';

@Component({
selector: 'vdr-floating-button',
templateUrl: './floating-button.component.html',
styleUrl: './floating-button.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FloatingButtonComponent implements OnInit, OnDestroy {
constructor(
private floatingButtonService: FloatingButtonService,
private router: Router,
private cdr: ChangeDetectorRef,
) {}
floatingButtons: FloatingButton[] = [];
private routerSubscription: Subscription;

handleClick(button: FloatingButton) {
this.floatingButtonService.handleClick(button);
}

shouldShowButton(button: FloatingButton): boolean {
if (!button.routes || button.routes.length === 0 || button.routes === '*') {
return true;
}

const routes = Array.isArray(button.routes) ? button.routes : [button.routes];
const currentRoute = this.router.url;
const result = routes.some(route => {
if (route === '*') {
return true;
}
const regexPattern = route
.replace(/\./g, '\\.')
.replace(/\-/g, '\\-')
.replace(/\*/g, '.*')
.replace(/\?/g, '.')
.replace(/\+/g, '.+')
.replace(/\(/g, '\\(')
.replace(/\)/g, '\\)');

const pattern = new RegExp('.*' + regexPattern + '$');
return pattern.test(currentRoute);
});
return result;
}

ngOnInit() {
this.floatingButtonService.getFloatingButtons().subscribe(buttons => {
this.floatingButtons = buttons;
this.updateButtonVisibility();
});
this.routerSubscription = this.router.events.subscribe(event => {
if (event instanceof NavigationEnd) {
this.updateButtonVisibility();
}
});
}

ngOnDestroy() {
if (this.routerSubscription) {
this.routerSubscription.unsubscribe();
}
}

private updateButtonVisibility() {
this.floatingButtons.forEach(button => {
button.visible = this.shouldShowButton(button);
});
this.cdr.detectChanges();
}
}
2 changes: 2 additions & 0 deletions packages/admin-ui/src/lib/core/src/core.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { LocalStorageService } from './providers/local-storage/local-storage.ser
import { Permission } from './public_api';
import { registerDefaultFormInputs } from './shared/dynamic-form-inputs/default-form-inputs';
import { SharedModule } from './shared/shared.module';
import { FloatingButtonComponent } from './components/floating-button/floating-button.component';

@NgModule({
imports: [
Expand All @@ -53,6 +54,7 @@ import { SharedModule } from './shared/shared.module';
AppShellComponent,
UserMenuComponent,
BaseNavComponent,
FloatingButtonComponent,
MainNavComponent,
SettingsNavComponent,
BreadcrumbComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Provider } from '@angular/core';
import { APP_INITIALIZER } from '@angular/core';
import { FloatingButton } from '../providers/floating-button/floating-button-types';

import { FloatingButtonService } from '../providers/floating-button/floating-button.service';

export function addFloatingButton(config: Omit<FloatingButton, 'visible'>): Provider {
return {
provide: APP_INITIALIZER,
multi: true,
useFactory: (floatingButtonService: FloatingButtonService) => () => {
floatingButtonService.addFloatingButton(config);
},
deps: [FloatingButtonService],
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Injector } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

export interface FloatingButtonFunctionContext<ComponentType> {
injector: Injector;
}

export interface FloatingButton<ComponentType = any> {
onClick: (injector: Injector) => void;
routes?: string[] | string;
requiresPermission?: string | ((userPermissions: string[]) => boolean);
disabled?: boolean;
icon?: string;
iconSize?: number;
iconColor?: string;
buttonSize?: number;
backgroundColor?: string;
visible?: boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Injectable, Injector } from '@angular/core';
import { Observable, of } from 'rxjs';
import { ActivatedRoute } from '@angular/router';
import { DataService } from '../../data/providers/data.service';
import { FloatingButton } from './floating-button-types';
@Injectable({
providedIn: 'root',
})
export class FloatingButtonService {
constructor(
private injector: Injector,
private dataService: DataService,
) {}

handleClick(button: FloatingButton) {
button.onClick(this.injector);
}

getFloatingButtons(): Observable<FloatingButton[]> {
return of(this.addedFloatingButtons);
}
private addedFloatingButtons: FloatingButton[] = [];

addFloatingButton(config: Omit<FloatingButton, 'visible'>) {
this.addedFloatingButtons.push(config);
}
}
4 changes: 4 additions & 0 deletions packages/admin-ui/src/lib/core/src/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export * from './components/app-shell/app-shell.component';
export * from './components/base-nav/base-nav.component';
export * from './components/breadcrumb/breadcrumb.component';
export * from './components/channel-switcher/channel-switcher.component';
export * from './components/floating-button/floating-button.component';
export * from './components/main-nav/main-nav.component';
export * from './components/notification/notification.component';
export * from './components/overlay-host/overlay-host.component';
Expand Down Expand Up @@ -77,6 +78,7 @@ export * from './data/utils/remove-readonly-custom-fields';
export * from './data/utils/transform-relation-custom-field-inputs';
export * from './extension/add-action-bar-dropdown-menu-item';
export * from './extension/add-action-bar-item';
export * from './extension/add-floating-button';
export * from './extension/add-nav-menu-item';
export * from './extension/components/angular-route.component';
export * from './extension/components/route.component';
Expand Down Expand Up @@ -111,6 +113,8 @@ export * from './providers/data-table/data-table-filter-collection';
export * from './providers/data-table/data-table-filter';
export * from './providers/data-table/data-table-sort-collection';
export * from './providers/data-table/data-table-sort';
export * from './providers/floating-button/floating-button-types';
export * from './providers/floating-button/floating-button.service';
export * from './providers/guard/auth.guard';
export * from './providers/health-check/health-check.service';
export * from './providers/i18n/custom-http-loader';
Expand Down
Loading