@@ -33,21 +33,23 @@
{{ element.email }}
@if (element.emailVerified) {
-
verified
+
}
Name
- {{ element.displayName }}
+
+ {{ element.displayName }}
+
Active
@if (element.disabled) {
- block
+
} @else {
- check
+
}
@@ -57,10 +59,10 @@
@for (provider of element.providers; track provider) {
@switch (provider) {
@case ('password') {
-
email
+
}
@default {
-
verified_user
+
}
}
}
@@ -93,11 +95,11 @@
Actions
-
diff --git a/src/app/features/admin/users/users.component.scss b/src/app/features/admin/users/users.component.scss
index 9d2cad55..b93454f8 100644
--- a/src/app/features/admin/users/users.component.scss
+++ b/src/app/features/admin/users/users.component.scss
@@ -33,6 +33,6 @@ mat-cell {
}
&.mat-column-actions {
- max-width: 125px;
+ max-width: 115px;
}
}
diff --git a/src/app/features/admin/users/users.component.ts b/src/app/features/admin/users/users.component.ts
index ea8b67f9..d42f684f 100644
--- a/src/app/features/admin/users/users.component.ts
+++ b/src/app/features/admin/users/users.component.ts
@@ -1,23 +1,26 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, inject, OnInit, signal, viewChild } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
-import { MatButtonModule } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
-import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSort, MatSortModule } from '@angular/material/sort';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
-import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTooltipModule } from '@angular/material/tooltip';
+import { provideIcons } from '@ng-icons/core';
+import { lucideCheck, lucideMail, lucidePencil, lucideRefreshCcw, lucideTrash, lucideUserPlus, lucideX } from '@ng-icons/lucide';
import { ConfirmationDialogComponent } from '@shared/components/confirmation-dialog/confirmation-dialog.component';
import { ConfirmationDialogModel } from '@shared/components/confirmation-dialog/confirmation-dialog.model';
-import { AnimateDirective } from '@shared/directives/animate.directive';
import { User } from '@shared/models/user.model';
import { NotificationService } from '@shared/services/notification.service';
import { UserService } from '@shared/services/user.service';
+import { HlmButtonImports } from '@spartan-ng/helm/button';
+import { HlmIconImports } from '@spartan-ng/helm/icon';
+import { HlmProgressImports } from '@spartan-ng/helm/progress';
+import { HlmSpinnerImports } from '@spartan-ng/helm/spinner';
+import { HlmTooltipImports } from '@spartan-ng/helm/tooltip';
import { filter, switchMap } from 'rxjs/operators';
import { UserDialogComponent } from './user-dialog/user-dialog.component';
import { UserDialogModel } from './user-dialog/user-dialog.model';
@@ -30,18 +33,30 @@ import { UserInviteDialogResponse } from './user-invite-dialog/user-invite-dialo
styleUrls: ['./users.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
- MatToolbarModule,
MatTooltipModule,
MatTableModule,
- MatProgressBarModule,
MatFormFieldModule,
MatInputModule,
MatSortModule,
MatIconModule,
CommonModule,
- MatButtonModule,
MatPaginatorModule,
- AnimateDirective,
+ HlmButtonImports,
+ HlmIconImports,
+ HlmTooltipImports,
+ HlmProgressImports,
+ HlmSpinnerImports,
+ ],
+ providers: [
+ provideIcons({
+ lucideUserPlus,
+ lucideRefreshCcw,
+ lucidePencil,
+ lucideTrash,
+ lucideCheck,
+ lucideX,
+ lucideMail,
+ }),
],
})
export class UsersComponent implements OnInit {
diff --git a/src/app/features/features-routing.module.ts b/src/app/features/features-routing.module.ts
index f8e5d5e9..a35b6870 100644
--- a/src/app/features/features-routing.module.ts
+++ b/src/app/features/features-routing.module.ts
@@ -1,10 +1,11 @@
import { NgModule } from '@angular/core';
import { AuthGuard, customClaims } from '@angular/fire/auth-guard';
import { RouterModule, Routes } from '@angular/router';
+import { BreadcrumbItem } from '@shared/models/breadcrumb.model';
import { UserPermission } from '@shared/models/user.model';
import { pipe } from 'rxjs';
import { map } from 'rxjs/operators';
-import { FeaturesComponent } from './features.component';
+import FeaturesComponent from './features.component';
const ROLE_ADMIN = 'admin';
const ROLE_CUSTOM = 'custom';
@@ -154,6 +155,12 @@ const routes: Routes = [
path: 'welcome',
title: 'Welcome',
loadComponent: () => import('./welcome/welcome.component').then(m => m.WelcomeComponent),
+ data: {
+ breadcrumb: {
+ label: 'Welcome',
+ route: '/welcome',
+ } satisfies BreadcrumbItem,
+ },
},
{
path: 'spaces/:spaceId/dashboard',
@@ -167,6 +174,10 @@ const routes: Routes = [
canActivate: [AuthGuard],
data: {
authGuardPipe: hasPermissionTranslationRead,
+ breadcrumb: {
+ label: 'Translations',
+ helpUrl: 'https://localess.org/docs/translations/overview',
+ } satisfies BreadcrumbItem,
},
},
{
@@ -176,6 +187,10 @@ const routes: Routes = [
canActivate: [AuthGuard],
data: {
authGuardPipe: hasPermissionContentRead,
+ breadcrumb: {
+ label: 'Contents',
+ helpUrl: 'https://localess.org/docs/content/overview',
+ } satisfies BreadcrumbItem,
},
},
{
@@ -185,6 +200,10 @@ const routes: Routes = [
canActivate: [AuthGuard],
data: {
authGuardPipe: hasPermissionAssetRead,
+ breadcrumb: {
+ label: 'Assets',
+ helpUrl: 'https://localess.org/docs/assets/overview',
+ } satisfies BreadcrumbItem,
},
},
{
@@ -194,6 +213,10 @@ const routes: Routes = [
canActivate: [AuthGuard],
data: {
authGuardPipe: hasPermissionSchemaRead,
+ breadcrumb: {
+ label: 'Schemas',
+ helpUrl: 'https://localess.org/docs/schemas/overview',
+ } satisfies BreadcrumbItem,
},
},
{
@@ -203,12 +226,20 @@ const routes: Routes = [
canActivate: [AuthGuard],
data: {
authGuardPipe: hasPermissionTranslationRead,
+ breadcrumb: {
+ label: 'Tasks',
+ } satisfies BreadcrumbItem,
},
},
{
path: 'spaces/:spaceId/open-api',
title: 'Open API',
loadChildren: () => import('./spaces/open-api/open-api.module').then(m => m.OpenApiModule),
+ data: {
+ breadcrumb: {
+ label: 'Open API',
+ } satisfies BreadcrumbItem,
+ },
},
{
path: 'spaces/:spaceId/settings',
@@ -217,6 +248,9 @@ const routes: Routes = [
canActivate: [AuthGuard],
data: {
authGuardPipe: hasPermissionSpaceManagement,
+ breadcrumb: {
+ label: 'Settings',
+ } satisfies BreadcrumbItem,
},
},
{
@@ -226,6 +260,9 @@ const routes: Routes = [
canActivate: [AuthGuard],
data: {
authGuardPipe: hasPermissionUserManagement,
+ breadcrumb: {
+ label: 'Users',
+ } satisfies BreadcrumbItem,
},
},
{
@@ -235,6 +272,9 @@ const routes: Routes = [
canActivate: [AuthGuard],
data: {
authGuardPipe: hasPermissionSpaceManagement,
+ breadcrumb: {
+ label: 'Spaces',
+ } satisfies BreadcrumbItem,
},
},
{
@@ -244,6 +284,9 @@ const routes: Routes = [
canActivate: [AuthGuard],
data: {
authGuardPipe: hasPermissionSettingsManagement,
+ breadcrumb: {
+ label: 'Settings',
+ } satisfies BreadcrumbItem,
},
},
],
diff --git a/src/app/features/features.component.html b/src/app/features/features.component.html
index 9db3b108..c6439884 100644
--- a/src/app/features/features.component.html
+++ b/src/app/features/features.component.html
@@ -1,153 +1,245 @@
-
-
-
- {{ settingsStore.mainMenuExpended() ? 'menu_open' : 'menu' }}
-
-
- @if (appSettingsStore.ui(); as ui) {
- @if (ui.text) {
-
-
- {{ ui.text }}
-
- }
- }
-
-
- @if (spaceStore.spaces(); as spaces) {
- @if (spaceStore.selectedSpace(); as selectedSpace) {
-
- @if (selectedSpace.icon) {
- {{ selectedSpace.icon }}
- }
- {{ selectedSpace.name }}
- arrow_drop_down
-
-
- @for (space of spaces; track space.id) {
-
- @if (space.icon) {
- {{ space.icon }}
- }
- {{ space.name }}
-
+
+
+
+
+
+
+ @if (sidebarService.open() || sidebarService.openMobile()) {
+ @if (appSettingsStore.ui(); as ui) {
+ @if (ui.text) {
+
+
+ {{ ui.text }}
+
+
}
-
+ }
}
- } @else {
-
No Spaces. Click here to create one.
- }
-
-
-
-
- waving_hand
-
-
- volunteer_activism
-
-
- public
-
-
- help_center
-
-
- @for (item of communitySideMenu; track item.label) {
-
- {{ item.icon }}
- {{ item.label }}
-
+
+
+
+
+
+ @if (spaceStore.spaces().length > 0) {
+ -
+
+
+ {{ spaceStore.selectedSpace()?.name }}
+
+
+
+
+ @for (space of spaceStore.spaces(); track space.id) {
+
+
+ {{ space.name }}
+
+ }
+
+
+
+ }
+
+
+
+ @if (spaceStore.selectedSpace()) {
+
+
Space
+
+
+ @for (item of userSideMenu(); track item.label) {
+ @if (item.permission | canUserPerform | async) {
+ -
+
+
+ {{ item.label }}
+
+
+ }
+ }
+
+
+
}
-
-
-
- dark_mode
- light_mode
-
-
- settings
-
-
- account_circle
-
-
- logout
-
-
-
-
-
-
- @if (spaceStore.selectedSpace(); as selectedSpace) {
- @for (item of userSideMenu(); track item.label) {
- @if (item.permission | canUserPerform | async) {
-
- {{ item.icon }}
- @if (settingsStore.mainMenuExpended()) {
- {{ item.label }}
+ @if (['USER_MANAGEMENT', 'SPACE_MANAGEMENT'] | canUserPerform | async) {
+
+
Admin
+
+
+ @for (item of adminSideMenu; track item.label) {
+ @if (item.permission | canUserPerform | async) {
+ -
+
+
+ {{ item.label }}
+
+
}
-
- }
- }
- }
-
- @if (['USER_MANAGEMENT', 'SPACE_MANAGEMENT'] | canUserPerform | async) {
-
- @if (settingsStore.mainMenuExpended()) {
- Admin
- }
- @for (item of adminSideMenu; track item.label) {
- @if (item.permission | canUserPerform | async) {
-
- {{ item.icon }}
- @if (settingsStore.mainMenuExpended()) {
- {{ item.label }}
+ }
+
+
+
+ }
+
+
+
+ -
+
+
+ Welcome
+
+
+ -
+
+
+ Support
+
+
+
+
+
+ @for (menuItem of communitySideMenu; track menuItem.label) {
+
+
+ {{ menuItem.label }}
+
+
}
-
- }
- }
- }
+
+
-
- @if (latestRelease?.tag_name > 'v' + version) {
-
- release_alert
- @if (settingsStore.mainMenuExpended()) {
- Version : {{ version }}
+ -
+
+
+ Sponsor Us
+
+
+ @if (latestRelease && latestRelease.tag_name > 'v' + version) {
+ -
+
+
+ Version: {{ version }}
+
+
+ } @else {
+ -
+
+
+ Version: {{ version }}
+
+
}
-
- } @else {
-
- new_releases
- @if (settingsStore.mainMenuExpended()) {
- Version : {{ version }}
+
+
+
+
+
+
+ -
+
+
+
+ {{ userStore.initials() }}
+
+
+ {{ userStore.displayName() }}
+ {{ userStore.email() }}
+
+
+
+
+
+
+
+
+ {{ userStore.initials() }}
+
+
+ {{ userStore.displayName() }}
+ {{ userStore.email() }}
+
+
+
+
+
+ Profile
+
+
+
+
+ Log out
+
+
+
+
+
+
+
+
+
+ @if (userStore.role() === undefined) {
+ Please contact your administrator to grant you access to resources.
+ }
+
+
+
+
+
+ Debug Settings
+
+
+
+
diff --git a/src/app/features/features.component.scss b/src/app/features/features.component.scss
index 39ce7cdc..e69de29b 100644
--- a/src/app/features/features.component.scss
+++ b/src/app/features/features.component.scss
@@ -1,38 +0,0 @@
-.wrapper {
- height: calc(100% - 64px);
-}
-
-mat-sidenav-container {
- height: 100%;
- // when side-nav is collapsed
- mat-sidenav {
- width: 200px;
- border-right: 1px solid var(--mat-sys-outline-variant);
- border-radius: 0;
- &.collapsed {
- width: 56px;
- mat-list-item {
- padding-right: 0;
- mat-icon {
- margin-right: 16px;
- }
- }
- }
- }
- mat-sidenav-content {
- }
-}
-
-mat-form-field {
- width: 100%;
-}
-
-mat-icon.flash {
- color: var(--mat-sys-primary)
-}
-
-mat-list-item {
- &.active {
- background: var(--mat-sys-primary-container);
- }
-}
diff --git a/src/app/features/features.component.ts b/src/app/features/features.component.ts
index 8f4275bd..3c9243a0 100644
--- a/src/app/features/features.component.ts
+++ b/src/app/features/features.component.ts
@@ -1,44 +1,89 @@
import { CommonModule } from '@angular/common';
-import {
- ChangeDetectionStrategy,
- ChangeDetectorRef,
- Component,
- computed,
- DestroyRef,
- effect,
- inject,
- OnInit,
- signal,
- Signal,
-} from '@angular/core';
+import { ChangeDetectionStrategy, Component, computed, DestroyRef, effect, inject, signal, Signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Auth, signOut } from '@angular/fire/auth';
-import { MatButtonModule } from '@angular/material/button';
-import { MatDialog } from '@angular/material/dialog';
-import { MatDividerModule } from '@angular/material/divider';
-import { MatIconModule } from '@angular/material/icon';
-import { MatListModule } from '@angular/material/list';
-import { MatMenuModule } from '@angular/material/menu';
-import { MatSidenavModule } from '@angular/material/sidenav';
-import { MatSlideToggleModule } from '@angular/material/slide-toggle';
-import { MatToolbarModule } from '@angular/material/toolbar';
-import { MatTooltipModule } from '@angular/material/tooltip';
-import { Router, RouterModule } from '@angular/router';
+import { ReactiveFormsModule } from '@angular/forms';
+import { ActivatedRoute, NavigationEnd, Router, RouterModule } from '@angular/router';
+import { IconType, provideIcons } from '@ng-icons/core';
+import {
+ lucideBadgeInfo,
+ lucideBookOpen,
+ lucideChevronDown,
+ lucideChevronsUpDown,
+ lucideCircleQuestionMark,
+ lucideCode,
+ lucideEarth,
+ lucideExternalLink,
+ lucideFileCheck,
+ lucideGalleryHorizontal,
+ lucideGauge,
+ lucideHeartHandshake,
+ lucideImage,
+ lucideLanguages,
+ lucideLifeBuoy,
+ lucideLogOut,
+ lucideMoon,
+ lucideSend,
+ lucideSettings,
+ lucideShieldAlert,
+ lucideShieldCheck,
+ lucideSun,
+ lucideToyBrick,
+ lucideUserCircle,
+ lucideUsers,
+} from '@ng-icons/lucide';
+import { tablerApi, tablerSpaces } from '@ng-icons/tabler-icons';
+import { LogoComponent } from '@shared/components/logo';
import { Release } from '@shared/generated/github/models/release';
import { ReposService } from '@shared/generated/github/services/repos.service';
+import { BreadcrumbItem } from '@shared/models/breadcrumb.model';
import { Space } from '@shared/models/space.model';
import { USER_PERMISSIONS_IMPORT_EXPORT, UserPermission } from '@shared/models/user.model';
import { CanUserPerformPipe } from '@shared/pipes/can-user-perform.pipe';
+import { ContentService } from '@shared/services/content.service';
+import { SchemaService } from '@shared/services/schema.service';
import { AppSettingsStore } from '@shared/stores/app-settings.store';
import { LocalSettingsStore } from '@shared/stores/local-settings.store';
import { SpaceStore } from '@shared/stores/space.store';
import { UserStore } from '@shared/stores/user.store';
-import browser from 'browser-detect';
+import { BrnSheetImports } from '@spartan-ng/brain/sheet';
+import { BrnTooltipImports } from '@spartan-ng/brain/tooltip';
+import { HlmAvatarImports } from '@spartan-ng/helm/avatar';
+import { HlmBreadCrumbImports } from '@spartan-ng/helm/breadcrumb';
+import { HlmButtonImports } from '@spartan-ng/helm/button';
+import { HlmDropdownMenuImports } from '@spartan-ng/helm/dropdown-menu';
+import { HlmField, HlmFieldGroup, HlmFieldLabel, HlmFieldSet } from '@spartan-ng/helm/field';
+import { HlmIconImports } from '@spartan-ng/helm/icon';
+import { HlmSeparatorImports } from '@spartan-ng/helm/separator';
+import { HlmSheetImports } from '@spartan-ng/helm/sheet';
+import { HlmSidebarImports, HlmSidebarService } from '@spartan-ng/helm/sidebar';
+import { HlmSwitch } from '@spartan-ng/helm/switch';
+import { HlmTooltipImports } from '@spartan-ng/helm/tooltip';
+import { cva } from 'class-variance-authority';
+import { filter } from 'rxjs';
import { environment } from '../../environments/environment';
+const appTextVariants = cva(
+ 'focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-md border px-2 py-0.5 text-xl font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] ',
+ {
+ variants: {
+ variant: {
+ primary: 'bg-primary text-primary-foreground [a&]:hover:bg-primary/90 border-transparent',
+ secondary: 'bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90 border-transparent',
+ destructive:
+ 'bg-destructive [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 border-transparent text-white',
+ outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground',
+ },
+ },
+ defaultVariants: {
+ variant: 'primary',
+ },
+ },
+);
+
interface SideMenuItem {
- icon: string;
+ icon: IconType;
link: string;
label: string;
permission?: UserPermission | UserPermission[];
@@ -51,35 +96,80 @@ interface SideMenuItem {
styleUrl: './features.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
- MatToolbarModule,
- MatButtonModule,
- MatIconModule,
- MatTooltipModule,
- MatMenuModule,
RouterModule,
- MatSidenavModule,
CanUserPerformPipe,
CommonModule,
- MatListModule,
- MatSlideToggleModule,
- MatDividerModule,
+ LogoComponent,
+ HlmSidebarImports,
+ HlmIconImports,
+ HlmTooltipImports,
+ HlmDropdownMenuImports,
+ HlmAvatarImports,
+ BrnTooltipImports,
+ HlmButtonImports,
+ HlmSeparatorImports,
+ HlmBreadCrumbImports,
+ HlmSheetImports,
+ BrnSheetImports,
+ HlmFieldGroup,
+ HlmFieldSet,
+ HlmField,
+ HlmFieldLabel,
+ HlmSwitch,
+ ReactiveFormsModule,
+ ],
+ providers: [
+ provideIcons({
+ lucideGauge,
+ lucideLanguages,
+ lucideGalleryHorizontal,
+ lucideImage,
+ lucideToyBrick,
+ lucideFileCheck,
+ tablerApi,
+ lucideSettings,
+ lucideUsers,
+ tablerSpaces,
+ lucideChevronsUpDown,
+ lucideChevronDown,
+ lucideLogOut,
+ lucideUserCircle,
+ lucideShieldAlert,
+ lucideShieldCheck,
+ lucideBookOpen,
+ lucideSend,
+ lucideCircleQuestionMark,
+ lucideHeartHandshake,
+ lucideEarth,
+ lucideLifeBuoy,
+ lucideExternalLink,
+ lucideMoon,
+ lucideSun,
+ lucideCode,
+ lucideBadgeInfo,
+ }),
],
})
-export class FeaturesComponent implements OnInit {
- private readonly cd = inject(ChangeDetectorRef);
+class FeaturesComponent {
private readonly router = inject(Router);
private readonly reposService = inject(ReposService);
- private readonly dialog = inject(MatDialog);
private auth = inject(Auth);
+ private route = inject(ActivatedRoute);
+ private readonly contentService = inject(ContentService);
+ private readonly schemaService = inject(SchemaService);
+
+ public readonly sidebarService = inject(HlmSidebarService);
// Settings
isSettingsMenuExpended = signal(false);
isDebug = environment.debug;
+ showDebugSettings = signal(false);
- logo = 'assets/logo.png';
version = environment.version;
latestRelease?: Release;
+ appTextClass = computed(() => appTextVariants({ variant: this.appSettingsStore.ui()?.color }));
+
userSideMenu: Signal = computed(() => {
const selectedSpaceId = this.spaceStore.selectedSpaceId();
console.log('User Side Menu Computed : Selected Space Id :', selectedSpaceId);
@@ -87,19 +177,29 @@ export class FeaturesComponent implements OnInit {
console.log('User Side Menu Computed : User Permissions :', this.userStore.permissions());
if (selectedSpaceId) {
return [
- { link: `spaces/${selectedSpaceId}/dashboard`, label: 'Dashboard', icon: 'dashboard' },
+ { link: `spaces/${selectedSpaceId}/dashboard`, label: 'Dashboard', icon: 'lucideGauge' },
{
link: `spaces/${selectedSpaceId}/translations`,
label: 'Translations',
- icon: 'translate',
+ icon: 'lucideLanguages',
permission: UserPermission.TRANSLATION_READ,
},
- { link: `spaces/${selectedSpaceId}/contents`, label: 'Content', icon: 'web_stories', permission: UserPermission.CONTENT_READ },
- { link: `spaces/${selectedSpaceId}/assets`, label: 'Assets', icon: 'attachment', permission: UserPermission.ASSET_READ },
- { link: `spaces/${selectedSpaceId}/schemas`, label: 'Schemas', icon: 'schema', permission: UserPermission.SCHEMA_READ },
- { link: `spaces/${selectedSpaceId}/tasks`, label: 'Tasks', icon: 'task', permission: USER_PERMISSIONS_IMPORT_EXPORT },
- { link: `spaces/${selectedSpaceId}/open-api`, label: 'Open API', icon: 'api', permission: UserPermission.DEV_OPEN_API },
- { link: `spaces/${selectedSpaceId}/settings`, label: 'Settings', icon: 'settings', permission: UserPermission.SPACE_MANAGEMENT },
+ {
+ link: `spaces/${selectedSpaceId}/contents`,
+ label: 'Content',
+ icon: 'lucideGalleryHorizontal',
+ permission: UserPermission.CONTENT_READ,
+ },
+ { link: `spaces/${selectedSpaceId}/assets`, label: 'Assets', icon: 'lucideImage', permission: UserPermission.ASSET_READ },
+ { link: `spaces/${selectedSpaceId}/schemas`, label: 'Schemas', icon: 'lucideToyBrick', permission: UserPermission.SCHEMA_READ },
+ { link: `spaces/${selectedSpaceId}/tasks`, label: 'Tasks', icon: 'lucideFileCheck', permission: USER_PERMISSIONS_IMPORT_EXPORT },
+ { link: `spaces/${selectedSpaceId}/open-api`, label: 'Open API', icon: 'tablerApi', permission: UserPermission.DEV_OPEN_API },
+ {
+ link: `spaces/${selectedSpaceId}/settings`,
+ label: 'Settings',
+ icon: 'lucideSettings',
+ permission: UserPermission.SPACE_MANAGEMENT,
+ },
];
} else {
return [];
@@ -107,15 +207,16 @@ export class FeaturesComponent implements OnInit {
});
adminSideMenu: SideMenuItem[] = [
- { link: 'admin/users', label: 'Users', icon: 'people', permission: UserPermission.USER_MANAGEMENT },
- { link: 'admin/spaces', label: 'Spaces', icon: 'space_dashboard', permission: UserPermission.SPACE_MANAGEMENT },
- { link: 'admin/settings', label: 'Settings', icon: 'settings', permission: UserPermission.SETTINGS_MANAGEMENT },
+ { link: 'admin/users', label: 'Users', icon: 'lucideUsers', permission: UserPermission.USER_MANAGEMENT },
+ { link: 'admin/spaces', label: 'Spaces', icon: 'tablerSpaces', permission: UserPermission.SPACE_MANAGEMENT },
+ { link: 'admin/settings', label: 'Settings', icon: 'lucideSettings', permission: UserPermission.SETTINGS_MANAGEMENT },
];
communitySideMenu: SideMenuItem[] = [
- { link: 'https://localess.org/docs/introduction', label: 'Documentation', icon: 'help' },
- { link: 'https://github.com/Lessify/localess', label: 'Code', icon: 'code' },
- { link: 'https://github.com/Lessify/localess/issues', label: 'Feedback', icon: 'forum' },
+ { link: 'https://localess.org/home', label: 'Visit Localess.ORG', icon: 'lucideEarth' },
+ { link: 'https://localess.org/docs/introduction', label: 'Documentation', icon: 'lucideBookOpen' },
+ { link: 'https://github.com/Lessify/localess', label: 'Code', icon: 'lucideCode' },
+ { link: 'https://github.com/Lessify/localess/issues', label: 'Feedback', icon: 'lucideSend' },
];
private destroyRef = inject(DestroyRef);
@@ -124,6 +225,8 @@ export class FeaturesComponent implements OnInit {
settingsStore = inject(LocalSettingsStore);
appSettingsStore = inject(AppSettingsStore);
+ breadcrumbs = signal([]);
+
constructor() {
const reposService = this.reposService;
@@ -142,16 +245,38 @@ export class FeaturesComponent implements OnInit {
await this.router.navigate(['login']);
}
});
- }
- private static isIEorEdgeOrSafari(): boolean {
- return ['ie', 'edge', 'safari'].includes(browser().name || '');
- }
+ effect(() => {
+ const selectedSpaceId = this.spaceStore.selectedSpaceId();
+ if (selectedSpaceId) {
+ this.contentService
+ .findAllDocuments(selectedSpaceId)
+ .pipe(takeUntilDestroyed(this.destroyRef))
+ .subscribe({
+ next: documents => {
+ this.spaceStore.updateDocuments(documents);
+ },
+ });
+ this.schemaService
+ .findAll(selectedSpaceId)
+ .pipe(takeUntilDestroyed(this.destroyRef))
+ .subscribe({
+ next: schemas => {
+ this.spaceStore.updateSchemas(schemas);
+ },
+ });
+ }
+ });
- ngOnInit(): void {
- if (FeaturesComponent.isIEorEdgeOrSafari()) {
- console.log('IE, Edge or Safari detected');
- }
+ this.router.events
+ .pipe(
+ filter(event => event instanceof NavigationEnd),
+ takeUntilDestroyed(this.destroyRef),
+ )
+ .subscribe(() => {
+ const breadcrumbs = this.buildBreadcrumbs(this.route.root);
+ this.breadcrumbs.set(breadcrumbs);
+ });
}
onSpaceSelection(space: Space): void {
@@ -163,10 +288,6 @@ export class FeaturesComponent implements OnInit {
return await signOut(this.auth);
}
- onMainMenuExpendedChangeState(): void {
- this.settingsStore.setMainMenuExpended(!this.settingsStore.mainMenuExpended());
- }
-
onSettingsMenuExpendedChangeState(): void {
this.isSettingsMenuExpended.update(it => !it);
}
@@ -182,4 +303,30 @@ export class FeaturesComponent implements OnInit {
switchTheme() {
this.settingsStore.setTheme(this.settingsStore.theme() === 'dark' ? 'light' : 'dark');
}
+
+ private buildBreadcrumbs(route: ActivatedRoute): BreadcrumbItem[] {
+ const breadcrumbs: BreadcrumbItem[] = [];
+ let currentRoute: ActivatedRoute | null = route;
+ while (currentRoute) {
+ if (currentRoute.routeConfig && currentRoute.routeConfig.data && currentRoute.routeConfig.data['breadcrumb']) {
+ const currentItem = currentRoute.routeConfig.data['breadcrumb'] as BreadcrumbItem | undefined;
+ if (currentItem) {
+ if (currentItem.route) {
+ // If route is defined in breadcrumb data, use it
+ breadcrumbs.push(currentItem);
+ } else {
+ // Otherwise, build the route from the current route snapshot
+ const urlSegments = currentRoute.snapshot.url.map(segment => segment.path).join('/');
+ const parentUrl = breadcrumbs.length > 0 ? breadcrumbs[breadcrumbs.length - 1].route || '' : '';
+ const fullPath = parentUrl.endsWith('/') || parentUrl === '' ? `${parentUrl}${urlSegments}` : `${parentUrl}/${urlSegments}`;
+ breadcrumbs.push({ ...currentItem, route: fullPath });
+ }
+ }
+ }
+ currentRoute = currentRoute.firstChild;
+ }
+ return breadcrumbs;
+ }
}
+
+export default FeaturesComponent;
diff --git a/src/app/features/me/me-routing.module.ts b/src/app/features/me/me-routing.module.ts
index 9e4dc3c3..05ff0ea5 100644
--- a/src/app/features/me/me-routing.module.ts
+++ b/src/app/features/me/me-routing.module.ts
@@ -1,11 +1,18 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
+import { BreadcrumbItem } from '@shared/models/breadcrumb.model';
import { MeComponent } from './me.component';
const routes: Routes = [
{
path: '',
component: MeComponent,
+ data: {
+ breadcrumb: {
+ label: 'Me',
+ route: '',
+ } satisfies BreadcrumbItem,
+ },
},
];
diff --git a/src/app/features/me/me.component.html b/src/app/features/me/me.component.html
index f9ee3d32..a9f0d00e 100644
--- a/src/app/features/me/me.component.html
+++ b/src/app/features/me/me.component.html
@@ -1,10 +1,3 @@
-
-
- Me
-
-
-
-
@if (userStore; as user) {
diff --git a/src/app/features/me/me.component.ts b/src/app/features/me/me.component.ts
index bb1f9a8d..c5a130ab 100644
--- a/src/app/features/me/me.component.ts
+++ b/src/app/features/me/me.component.ts
@@ -4,7 +4,6 @@ import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatDialog } from '@angular/material/dialog';
import { MatDividerModule } from '@angular/material/divider';
-import { MatToolbarModule } from '@angular/material/toolbar';
import { MeService } from '@shared/services/me.service';
import { NotificationService } from '@shared/services/notification.service';
import { UserStore } from '@shared/stores/user.store';
@@ -21,7 +20,7 @@ import { MePasswordDialogModel } from './me-password-dialog/me-password-dialog.m
templateUrl: './me.component.html',
styleUrls: ['./me.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
- imports: [MatToolbarModule, MatCardModule, CommonModule, MatDividerModule, MatButtonModule, NgOptimizedImage],
+ imports: [MatCardModule, CommonModule, MatDividerModule, MatButtonModule, NgOptimizedImage],
})
export class MeComponent {
private readonly dialog = inject(MatDialog);
diff --git a/src/app/features/spaces/assets/add-folder-dialog/add-folder-dialog.component.html b/src/app/features/spaces/assets/add-folder-dialog/add-folder-dialog.component.html
index 5cbe5501..19c870a7 100644
--- a/src/app/features/spaces/assets/add-folder-dialog/add-folder-dialog.component.html
+++ b/src/app/features/spaces/assets/add-folder-dialog/add-folder-dialog.component.html
@@ -4,8 +4,8 @@
Create new Folder