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

Improve providers for overriding #93

Merged
merged 4 commits into from
May 1, 2024
Merged
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
19 changes: 0 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,6 @@ export class MyDataService implements IDataService<MyDataType> {
}
```

`MyDataType` can be any type of data that reflects the file or directory metadata. It can be as simple as `string` or an object with properties.
`NAME_FUNCTION` must be provided if `MyDataType` is an object. It is a function that returns the name of the file or directory, to be displayed in the UI.

Example:

```Typescript
export interface MyDataType {
name: string;
path: string;
createdOn: Date;
}

// In this case, the name function will be:
{
provide: NAME_FUNCTION,
useValue: (node: INode) => node.data['name'],
}
```

And provide the implementation:

```TypeScript
Expand Down
2 changes: 1 addition & 1 deletion projects/ngx-explorer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ngx-explorer",
"version": "4.3.0",
"version": "4.5.0",
"peerDependencies": {
"@angular/common": "^17.2.0",
"@angular/core": "^17.2.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Directive, OnDestroy, inject } from '@angular/core';
import { Subscription } from 'rxjs';
import { INode, NgeExplorerConfig } from '../../shared/types';
import { CONFIG, NAME_FUNCTION } from '../../shared/providers';
import { CONFIG } from '../../shared/providers';
import { ExplorerService } from '../../services/explorer.service';

@Directive()
Expand All @@ -14,7 +14,6 @@ export class BaseView implements OnDestroy {

protected explorerService: ExplorerService = inject(ExplorerService);
protected config: NgeExplorerConfig = inject(CONFIG);
protected getName: (node: INode) => string = inject(NAME_FUNCTION);

constructor() {
this.subs.add(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ChangeDetectionStrategy, Component, Inject, ViewEncapsulation } from '@angular/core';
import { ChangeDetectionStrategy, Component, ViewEncapsulation, inject } from '@angular/core';
import { map } from 'rxjs';
import { INode, NgeExplorerConfig } from '../../shared/types';
import { INode } from '../../shared/types';
import { ExplorerService } from '../../services/explorer.service';
import { CONFIG, NAME_FUNCTION } from '../../shared/providers';
import { CONFIG } from '../../shared/providers';
import { AsyncPipe } from '@angular/common';

interface Breadcrumb {
Expand All @@ -20,6 +20,9 @@ interface Breadcrumb {
imports: [AsyncPipe],
})
export class BreadcrumbsComponent {
private explorerService = inject(ExplorerService);
private config = inject(CONFIG);

public breadcrumbs$ = this.explorerService.openedDir$.pipe(
map((n) => {
if (!n) {
Expand All @@ -28,20 +31,14 @@ export class BreadcrumbsComponent {
const pieces = [] as Breadcrumb[];
let currentNode = n;
while (currentNode.parentId) {
pieces.unshift({ name: this.getName(currentNode) || this.config.homeNodeName || '', node: currentNode });
pieces.unshift({ name: currentNode.name || this.config.homeNodeName || '', node: currentNode });
currentNode = this.explorerService.getNode(currentNode.parentId);
}
pieces.unshift({ name: this.getName(currentNode) || this.config.homeNodeName || '', node: currentNode });
pieces.unshift({ name: currentNode.name || this.config.homeNodeName || '', node: currentNode });
return pieces;
})
);

constructor(
private explorerService: ExplorerService,
@Inject(NAME_FUNCTION) private getName: (node: INode) => string,
@Inject(CONFIG) private config: NgeExplorerConfig
) {}

public click(crumb: Breadcrumb) {
this.explorerService.openNode(crumb.node.id);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { AsyncPipe, NgComponentOutlet } from '@angular/common';
import { Component, Inject } from '@angular/core';
import { Component, inject } from '@angular/core';
import { IconsComponent } from '../icons/icons.component';
import { ListComponent } from '../list/list.component';
import { MenuBarComponent } from '../menu-bar/menu-bar.component';
import { TreeComponent } from '../tree/tree.component';
import { map, BehaviorSubject } from 'rxjs';
import { CURRENT_VIEW, VIEWS } from '../../shared/providers';
import { View } from '../../shared/types';
import { VIEWS } from '../../shared/providers';
import { BreadcrumbsComponent } from '../breadcrumbs/breadcrumbs.component';
import { ExplorerService } from '../../services/explorer.service';
import { map } from 'rxjs';

@Component({
selector: 'nxe-content',
Expand All @@ -17,10 +17,7 @@ import { BreadcrumbsComponent } from '../breadcrumbs/breadcrumbs.component';
styleUrl: './content.component.scss',
})
export class ContentComponent {
public viewComponent$ = this.currentView$.pipe(map((view) => this.views.find((v) => v.name === view)!.component));

constructor(
@Inject(CURRENT_VIEW) private currentView$: BehaviorSubject<string>,
@Inject(VIEWS) protected views: View[]
) {}
protected explorerService = inject(ExplorerService);
protected views = inject(VIEWS);
public viewComponent$ = this.explorerService.currentView$.pipe(map((view) => this.views.find((v) => v.name === view)!.component));
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
<div class="nxe-icons-container">
@for (item of items; track $index) {
<div class="nxe-icons-wrapper" (dblclick)="open($event, item)" (click)="select($event, item)">
<div class="nxe-icons-wrapper-inner" [ngClass]="{ 'nxe-icon-selected': isSelected(item) }" [title]="getName(item)">
<div class="nxe-icons-wrapper-inner" [ngClass]="{ 'nxe-icon-selected': isSelected(item) }" [title]="item.name">
<div class="nxe-icons-icon">
<i [className]="item.isLeaf ? icons.leaf : icons.node"></i>
</div>
<div class="nxe-icon-text">{{ getName(item) }}</div>
<div class="nxe-icon-text">{{ item.name }}</div>
</div>
</div>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<span class="nxe-list-icon">
<i [className]="item.isLeaf ? icons.leaf : icons.node"></i>
</span>
{{ getName(item) }}
{{ item.name }}
</td>
<td></td>
<td></td>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Component, ElementRef, ViewChild, ViewEncapsulation, inject } from '@angular/core';
import { map, take } from 'rxjs';
import { INode, NgeExplorerConfig } from '../../shared/types';
import { NgeExplorerConfig } from '../../shared/types';
import { ExplorerService } from '../../services/explorer.service';
import { ViewSwitcherComponent } from '../view-switcher/view-switcher.component';
import { CONFIG, NAME_FUNCTION } from '../../shared/providers';
import { CONFIG } from '../../shared/providers';
import { AsyncPipe } from '@angular/common';

@Component({
Expand All @@ -18,7 +18,6 @@ export class MenuBarComponent {
@ViewChild('uploader', { static: true }) uploader!: ElementRef;

protected explorerService: ExplorerService = inject(ExplorerService);
protected getName: (node: INode) => string = inject(NAME_FUNCTION);
protected config: NgeExplorerConfig = inject(CONFIG);

protected featDelete = this.config.features?.delete;
Expand Down Expand Up @@ -49,7 +48,7 @@ export class MenuBarComponent {
map((n) => n[0])
)
.subscribe((node) => {
const oldName = this.getName(node);
const oldName = node.name;
const newName = prompt('Enter new name', oldName);
if (newName) {
this.explorerService.rename(newName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<div class="chevron" (click)="collapse(node)"><i class="nxe-angle-down" aria-hidden="true"></i></div>
}
<div class="dir-icon"><i class="nxe-folder" aria-hidden="true"></i></div>
<div class="dir-name" [innerText]="getName(node)"></div>
<div class="dir-name" [innerText]="node.name"></div>
</div>

<ng-container *ngTemplateOutlet="tree; context: { nodes: node.children }"> </ng-container>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Component, Inject, OnDestroy, ViewEncapsulation } from '@angular/core';
import { Component, OnDestroy, ViewEncapsulation, inject } from '@angular/core';
import { ExplorerService } from '../../services/explorer.service';
import { filter } from 'rxjs/operators';
import { Subscription } from 'rxjs';
import { INode } from '../../shared/types';
import { NgClass, NgTemplateOutlet } from '@angular/common';
import { NAME_FUNCTION } from '../../shared/providers';

@Component({
selector: 'nxe-tree',
Expand All @@ -15,15 +14,13 @@ import { NAME_FUNCTION } from '../../shared/providers';
imports: [NgTemplateOutlet, NgClass],
})
export class TreeComponent implements OnDestroy {
private explorerService = inject(ExplorerService);
protected treeNodes: INode[] = [];
protected expnadedIds = new Set<number>();
protected selectedId = -1;
private sub = new Subscription();

constructor(
private explorerService: ExplorerService,
@Inject(NAME_FUNCTION) protected getName: (node: INode) => string
) {
constructor() {
this.sub.add(
this.explorerService.root$.pipe(filter((x) => !!x)).subscribe((root) => {
this.expnadedIds.add(root.id); // always expand root
Expand Down Expand Up @@ -57,9 +54,10 @@ export class TreeComponent implements OnDestroy {
}

private buildTree(node: INode): INode {
const { id, parentId, data, isLeaf, children } = node;
const { id, parentId, name, data, isLeaf, children } = node;
const treeNode = {
id,
name: name,
parentId,
data,
isLeaf,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Component, Inject, ViewEncapsulation } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { View } from '../../shared/types';
import { CURRENT_VIEW, VIEWS } from '../../shared/providers';
import { Component, ViewEncapsulation, inject } from '@angular/core';
import { VIEWS } from '../../shared/providers';
import { ExplorerService } from '../../services/explorer.service';

@Component({
selector: 'nxe-view-switcher',
Expand All @@ -11,12 +10,10 @@ import { CURRENT_VIEW, VIEWS } from '../../shared/providers';
standalone: true,
})
export class ViewSwitcherComponent {
constructor(
@Inject(CURRENT_VIEW) private currentView: BehaviorSubject<string>,
@Inject(VIEWS) protected views: View[]
) {}
private explorerService = inject(ExplorerService);
protected views = inject(VIEWS);

setView(view: string) {
this.currentView.next(view);
this.explorerService.currentView$.next(view);
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { Directive, EventEmitter, HostListener, Output } from '@angular/core';
import { Directive, EventEmitter, HostListener, Output, inject } from '@angular/core';
import { ExplorerService } from '../services/explorer.service';

@Directive({
selector: '[nxeDragDrop]',
standalone: true,
})
export class DragDropDirective {
private explorerService = inject(ExplorerService);
@Output() dragEnter = new EventEmitter<any>();
@Output() dragOver = new EventEmitter<any>();
@Output() dragLeave = new EventEmitter<any>();
@Output() dragDrop = new EventEmitter<any>();
@Output() dragging = new EventEmitter<boolean>();

constructor(private explorerService: ExplorerService) {}

@HostListener('dragenter', ['$event'])
public onDragEnter(event: DragEvent) {
event.preventDefault();
Expand Down
1 change: 1 addition & 0 deletions projects/ngx-explorer/src/lib/services/data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export abstract class DataService implements IDataService<Data> {
abstract delete(data: Data[]): Observable<Data>;
abstract uploadFiles(data: Data, files: FileList): Observable<Data>;
abstract downloadFile(data: Data): Observable<Data>;
abstract getName(data: Data): string;
}
27 changes: 11 additions & 16 deletions projects/ngx-explorer/src/lib/services/explorer.service.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import { Inject, Injectable } from '@angular/core';
import { Injectable, inject } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { tap } from 'rxjs/operators';
import { INode, Dictionary, NgeExplorerConfig } from '../shared/types';
import { INode, Dictionary } from '../shared/types';
import { Utils } from '../shared/utils';
import { DataService } from './data.service';
import { CONFIG } from '../shared/providers';
import { CONFIG, VIEWS } from '../shared/providers';

@Injectable({
providedIn: 'root',
})
export class ExplorerService {
private internalTree = Utils.createNode();
private dataService = inject(DataService);
private config = inject(CONFIG);
private views = inject(VIEWS);
private internalTree = Utils.createNode(this.config.homeNodeName || 'Home');
private flatPointers: Dictionary<INode> = { [this.internalTree.id]: this.internalTree };

private readonly selectedNodes$$ = new BehaviorSubject<INode[]>([]);
private readonly openedNode$$ = new BehaviorSubject<INode | undefined>(undefined);
private readonly root$$ = new BehaviorSubject<INode>(this.internalTree);
public readonly currentView$ = new BehaviorSubject<string>(this.config.defaultView || this.views[0].name);

/**
* An Observable that emits the currently selected nodes in the explorer.
Expand All @@ -35,17 +39,8 @@ export class ExplorerService {
*/
public readonly root$ = this.root$$.asObservable();

constructor(
private dataService: DataService,
@Inject(CONFIG) private config: NgeExplorerConfig
) {
constructor() {
this.openNode(this.internalTree.id);

if (this.config.autoRefresh) {
setInterval(() => {
this.refresh();
}, this.config.autoRefreshInterval);
}
}

/**
Expand Down Expand Up @@ -190,8 +185,8 @@ export class ExplorerService {

return this.dataService.getContent(parent.data).pipe(
tap(({ files, dirs }) => {
const newDirNodes = dirs.map((data) => Utils.createNode(id, false, data));
const newFileNodes = files.map((data) => Utils.createNode(id, true, data));
const newDirNodes = dirs.map((data) => Utils.createNode(this.dataService.getName(data), id, false, data));
const newFileNodes = files.map((data) => Utils.createNode(this.dataService.getName(data), id, true, data));
const newChildren = newDirNodes.concat(newFileNodes);
const added = newChildren.filter((c) => !parent.children.find((o) => Utils.compareObjects(o.data, c.data)));
const removed = parent.children.filter((o) => !newChildren.find((c) => Utils.compareObjects(o.data, c.data)));
Expand Down
20 changes: 1 addition & 19 deletions projects/ngx-explorer/src/lib/shared/providers.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { InjectionToken, inject } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { INode, NgeExplorerConfig, View } from './types';
import { Data, NgeExplorerConfig, View } from './types';
import { IconsComponent, ListComponent } from '../../public-api';

export const DEFAULT_CONFIG: Partial<NgeExplorerConfig> = {
homeNodeName: 'Files',
autoRefresh: false,
autoRefreshInterval: 10000,
multipleSelection: true,
features: {
delete: true,
Expand Down Expand Up @@ -41,18 +38,3 @@ export const CONFIG = new InjectionToken<NgeExplorerConfig>('NXE_CONFIG', {
return { ...DEFAULT_CONFIG, defaultView } as NgeExplorerConfig;
},
});

export const CURRENT_VIEW = new InjectionToken<BehaviorSubject<string>>('NXE_CURRENT_VIEW', {
providedIn: 'root',
factory: () => {
const config = inject(CONFIG);
const views = inject(VIEWS);
const defaultView = config.defaultView || views[0].name;
return new BehaviorSubject<string>(defaultView);
},
});

export const NAME_FUNCTION = new InjectionToken<(node: INode) => string>('NXE_NAME_FUNCTION', {
providedIn: 'root',
factory: () => (node: INode) => node.data as unknown as string,
});
3 changes: 1 addition & 2 deletions projects/ngx-explorer/src/lib/shared/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface Data {

export interface INode {
id: number;
name: string;
parentId: number;
data: Data;
isLeaf: boolean;
Expand Down Expand Up @@ -61,8 +62,6 @@ export interface View {

export interface NgeExplorerConfig {
homeNodeName: string;
autoRefresh?: boolean;
autoRefreshInterval?: number;
defaultView: string;
multipleSelection?: boolean;
features?: {
Expand Down
Loading