-
+ @if (ephemeralFolder !== null) {
+
+ } @else {
+
+ }
true,
};
+const mockFilesystemService = {
+ getFolder: jasmine.createSpy().and.returnValue(Promise.resolve({})),
+};
+
describe('SharePreviewComponent', () => {
let component: SharePreviewComponent;
let fixture: ComponentFixture
;
@@ -122,6 +127,11 @@ describe('SharePreviewComponent', () => {
useValue: mockGoogleAnalyticsService,
});
+ config.providers.push({
+ provide: FilesystemService,
+ useValue: mockFilesystemService,
+ });
+
await TestBed.configureTestingModule(config).compileComponents();
dialog = TestBed.inject(DialogCdkService);
@@ -210,6 +220,7 @@ describe('SharePreviewComponent', () => {
const mockFileList = { itemClicked: new EventEmitter() };
+ component.isUnlistedShare = false;
component.subscribeToItemClicks(mockFileList);
mockFileList.itemClicked.emit({
item: new RecordVO({}),
diff --git a/src/app/share-preview/components/share-preview/share-preview.component.ts b/src/app/share-preview/components/share-preview/share-preview.component.ts
index 623c7630a..21418618c 100644
--- a/src/app/share-preview/components/share-preview/share-preview.component.ts
+++ b/src/app/share-preview/components/share-preview/share-preview.component.ts
@@ -30,6 +30,9 @@ import { PromptService } from '@shared/services/prompt/prompt.service';
import { Deferred } from '@root/vendor/deferred';
import { DialogCdkService } from '@root/app/dialog-cdk/dialog-cdk.service';
import { ShareLinksService } from '@root/app/share-links/services/share-links.service';
+import { FilesystemService } from '@root/app/filesystem/filesystem.service';
+import { DataService } from '@shared/services/data/data.service';
+import { ItemClickEvent } from '@fileBrowser/components/file-list/file-list.component';
import { CreateAccountDialogComponent } from '../create-account-dialog/create-account-dialog.component';
const MIN_PASSWORD_LENGTH = APP_CONFIG.passwordMinLength;
@@ -101,6 +104,7 @@ export class SharePreviewComponent implements OnInit, OnDestroy {
public hideBannerSubject: Subject = new Subject();
public hideBannerObservable = this.hideBannerSubject.asObservable();
public isUnlistedShare = false;
+ public ephemeralFolder: FolderVO | null = null;
constructor(
private router: Router,
@@ -114,6 +118,8 @@ export class SharePreviewComponent implements OnInit, OnDestroy {
private ga: GoogleAnalyticsService,
private dialog: DialogCdkService,
private shareLinksService: ShareLinksService,
+ private filesystemService: FilesystemService,
+ private dataService: DataService,
) {
this.shareToken = this.route.snapshot.params.shareToken;
@@ -190,6 +196,14 @@ export class SharePreviewComponent implements OnInit, OnDestroy {
this.shareLinksService.currentShareToken = this.shareToken;
this.isUnlistedShare = await this.shareLinksService.isUnlistedShare();
+ if (this.isUnlistedShare) {
+ this.ephemeralFolder = await this.filesystemService.getFolder(
+ this.route.snapshot.data.sharePreviewVO.FolderVO,
+ );
+ this.dataService.ephemeralFolder = this.ephemeralFolder;
+ this.dataService.pushBreadcrumbFolder(this.ephemeralFolder);
+ }
+
this.checkAccess();
if (!this.hasAccess) {
@@ -653,15 +667,44 @@ export class SharePreviewComponent implements OnInit, OnDestroy {
});
}
+ async showFolder(itemClickEvent: ItemClickEvent) {
+ if (itemClickEvent?.item?.isFolder) {
+ this.ephemeralFolder = await this.filesystemService.getFolder(
+ itemClickEvent?.item,
+ );
+ this.dataService.ephemeralFolder = this.ephemeralFolder;
+ this.dataService.pushBreadcrumbFolder(this.ephemeralFolder);
+ }
+ }
+
+ async goToFolderFromBreadcrumb(folderPosition: number) {
+ if (
+ folderPosition < 1 ||
+ folderPosition > this.dataService.breadcrumbFolders.length
+ ) {
+ return;
+ }
+ const newEphemeralFolder =
+ this.dataService.breadcrumbFolders[folderPosition - 1];
+ this.ephemeralFolder =
+ await this.filesystemService.getFolder(newEphemeralFolder);
+ this.dataService.ephemeralFolder = this.ephemeralFolder;
+ this.dataService.cutBreadcrumbRoute(this.ephemeralFolder);
+ }
+
subscribeToItemClicks(componentReference) {
if (!('itemClicked' in componentReference)) {
return;
}
this.fileListClickListener = componentReference.itemClicked.subscribe(
- () => {
- this.dispatchBannerClose();
- this.showCreateAccountDialog();
+ async (itemClickEvent: ItemClickEvent) => {
+ if (this.isUnlistedShare) {
+ this.showFolder(itemClickEvent);
+ } else {
+ this.dispatchBannerClose();
+ this.showCreateAccountDialog();
+ }
},
);
}
diff --git a/src/app/shared/components/breadcrumbs/breadcrumb.component.html b/src/app/shared/components/breadcrumbs/breadcrumb.component.html
index 84fab43c1..957bc0392 100644
--- a/src/app/shared/components/breadcrumbs/breadcrumb.component.html
+++ b/src/app/shared/components/breadcrumbs/breadcrumb.component.html
@@ -1,5 +1,9 @@
@if (!last) {
- {{ breadcrumb.text }}
+ @if (!isUnlistedShare) {
+ {{ breadcrumb.text }}
+ } @else {
+ {{ breadcrumb.text }}
+ }
}
@if (!last) {
diff --git a/src/app/shared/components/breadcrumbs/breadcrumb.component.ts b/src/app/shared/components/breadcrumbs/breadcrumb.component.ts
index bfd8633e7..980345545 100644
--- a/src/app/shared/components/breadcrumbs/breadcrumb.component.ts
+++ b/src/app/shared/components/breadcrumbs/breadcrumb.component.ts
@@ -6,6 +6,8 @@ import {
HostListener,
HostBinding,
Optional,
+ Output,
+ EventEmitter,
} from '@angular/core';
import {
DragTargetDroppableComponent,
@@ -14,6 +16,7 @@ import {
} from '@shared/services/drag/drag.service';
import { Subscription } from 'rxjs';
import debug from 'debug';
+import { ShareLinksService } from '@root/app/share-links/services/share-links.service';
import { Breadcrumb } from './breadcrumbs.component';
@Component({
@@ -27,12 +30,19 @@ export class BreadcrumbComponent
@Input() breadcrumb: Breadcrumb;
@Input() last: boolean;
+ @Output() breadcrumbClicked = new EventEmitter();
+
@HostBinding('class.drag-target') public isDragTarget = false;
@HostBinding('class.drop-target') public isDropTarget = false;
+ public isUnlistedShare: boolean;
+
private dragSubscription: Subscription;
private debug = debug('component:breadcrumb');
- constructor(@Optional() private drag: DragService) {
+ constructor(
+ @Optional() private drag: DragService,
+ private shareLinksService: ShareLinksService,
+ ) {
if (this.drag) {
this.dragSubscription = this.drag.events().subscribe((dragEvent) => {
this.onDragServiceEvent(dragEvent);
@@ -40,8 +50,9 @@ export class BreadcrumbComponent
}
}
- ngOnInit() {
+ async ngOnInit() {
this.debug('created %o', this.breadcrumb);
+ this.isUnlistedShare = await this.shareLinksService.isUnlistedShare();
}
ngOnDestroy() {
@@ -50,6 +61,10 @@ export class BreadcrumbComponent
}
}
+ goToFolder() {
+ this.breadcrumbClicked.emit(this.breadcrumb.folderPosition);
+ }
+
onDragServiceEvent(dragEvent: DragServiceEvent) {
switch (dragEvent.type) {
case 'start':
diff --git a/src/app/shared/components/breadcrumbs/breadcrumbs.component.html b/src/app/shared/components/breadcrumbs/breadcrumbs.component.html
index 96d462fe5..6927a448c 100644
--- a/src/app/shared/components/breadcrumbs/breadcrumbs.component.html
+++ b/src/app/shared/components/breadcrumbs/breadcrumbs.component.html
@@ -10,7 +10,12 @@
[class.breadcrumbs-large]="large"
>
@for (breadcrumb of breadcrumbs; track breadcrumb; let last = $last) {
-
+
}
@if (currentFolder?.ShareVOs?.length) {
diff --git a/src/app/shared/components/breadcrumbs/breadcrumbs.component.spec.ts b/src/app/shared/components/breadcrumbs/breadcrumbs.component.spec.ts
index 0bcd274b4..140a2264b 100644
--- a/src/app/shared/components/breadcrumbs/breadcrumbs.component.spec.ts
+++ b/src/app/shared/components/breadcrumbs/breadcrumbs.component.spec.ts
@@ -6,11 +6,17 @@ import { BreadcrumbsComponent } from '@shared/components/breadcrumbs/breadcrumbs
import { DataService } from '@shared/services/data/data.service';
import { Router } from '@angular/router';
import { FolderVO } from '@root/app/models';
+import { ShareLinksService } from '@root/app/share-links/services/share-links.service';
describe('BreadcrumbsComponent', () => {
let component: BreadcrumbsComponent;
let fixture: ComponentFixture;
let dataService: DataService;
+ const mockShareLinkService = {
+ isUnlistedShare: jasmine
+ .createSpy()
+ .and.returnValue(Promise.resolve(false)),
+ };
async function init(currentUrl?: string) {
TestBed.resetTestingModule();
@@ -18,6 +24,10 @@ describe('BreadcrumbsComponent', () => {
config.declarations.push(BreadcrumbsComponent);
config.providers.push(DataService);
+ config.providers.push({
+ provide: ShareLinksService,
+ useValue: mockShareLinkService,
+ });
config.providers.push({
provide: Router,
diff --git a/src/app/shared/components/breadcrumbs/breadcrumbs.component.ts b/src/app/shared/components/breadcrumbs/breadcrumbs.component.ts
index 21bfd81e0..8598bbbce 100644
--- a/src/app/shared/components/breadcrumbs/breadcrumbs.component.ts
+++ b/src/app/shared/components/breadcrumbs/breadcrumbs.component.ts
@@ -6,6 +6,8 @@ import {
Input,
ViewEncapsulation,
Optional,
+ Output,
+ EventEmitter,
} from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
@@ -15,16 +17,24 @@ import { DataService } from '@shared/services/data/data.service';
import { FolderVO } from '@root/app/models';
import debug from 'debug';
import { EditService } from '@core/services/edit/edit.service';
+import { ShareLinksService } from '@root/app/share-links/services/share-links.service';
export class Breadcrumb {
+ /**
+ * The position of the folder in the breadcrumb lineup
+ * This is not the index, because it starts from 1 and not from 0
+ */
+ public folderPosition: number;
public routerPath: string;
constructor(
+ folderPosition: number,
rootUrl: string,
public text: string,
public archiveNbr?: string,
public folder_linkId?: number,
rootUrlOnly = false,
) {
+ this.folderPosition = folderPosition;
if (rootUrlOnly) {
this.routerPath = rootUrl;
} else if (!archiveNbr && !folder_linkId) {
@@ -66,8 +76,11 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy {
@Input() darkText = false;
@Input() large = false;
+ @Output() breadcrumbClicked = new EventEmitter();
+
private scrollElement: Element;
private folderChangeListener: Subscription;
+ private isUnlistedShare: boolean = false;
private debug = debug('component:breadcrumbs');
constructor(
@@ -76,19 +89,23 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy {
private router: Router,
private route: ActivatedRoute,
@Optional() private edit: EditService,
+ private shareLinkService: ShareLinksService,
) {}
- ngOnInit() {
+ async ngOnInit() {
+ this.isUnlistedShare = await this.shareLinkService.isUnlistedShare();
this.scrollElement =
this.elementRef.nativeElement.querySelector('.breadcrumbs');
- this.setFolder(this.dataService.currentFolder);
+ this.setFolder(
+ this.dataService.ephemeralFolder || this.dataService.currentFolder,
+ );
setTimeout(() => {
this.scrollToEnd();
});
this.folderChangeListener = this.dataService.currentFolderChange.subscribe(
(folder: FolderVO) => {
- this.setFolder(folder);
+ this.setFolder(this.dataService.ephemeralFolder || folder);
setTimeout(() => {
this.scrollToEnd();
}, 0);
@@ -104,6 +121,10 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy {
this.folderChangeListener.unsubscribe();
}
+ breadcrumbItemClicked(breadcrumbId: number) {
+ this.breadcrumbClicked.emit(breadcrumbId);
+ }
+
setFolder(folder) {
this.currentFolder = folder;
this.breadcrumbs = [];
@@ -126,7 +147,10 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy {
this.router.routerState.snapshot.url.includes('/view/');
const showRootBreadcrumb =
- !isInPublic && !isInSharePreviewView && !isInSharePreviewInviteView;
+ !isInPublic &&
+ !isInSharePreviewView &&
+ !isInSharePreviewInviteView &&
+ !this.isUnlistedShare;
let rootUrl;
@@ -162,14 +186,14 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy {
}
if (showRootBreadcrumb) {
- this.breadcrumbs.push(new Breadcrumb(rootUrl, folder.pathAsText[0]));
+ this.breadcrumbs.push(new Breadcrumb(0, rootUrl, folder.pathAsText[0]));
if (this.breadcrumbs[0].routerPath === '/private')
this.breadcrumbs[0].text = 'Private';
}
if (isInPublicArchive) {
this.breadcrumbs.push(
- new Breadcrumb(rootUrl, folder.pathAsText[0], null, null, true),
+ new Breadcrumb(0, rootUrl, folder.pathAsText[0], null, null, true),
);
}
@@ -180,10 +204,11 @@ export class BreadcrumbsComponent implements OnInit, OnDestroy {
this.breadcrumbs.push(
new Breadcrumb(
+ i,
rootUrl,
- folder.pathAsText[i],
- folder.pathAsArchiveNbr[i],
- folder.pathAsFolder_linkId[i],
+ folder.pathAsText?.[i],
+ folder.pathAsArchiveNbr?.[i],
+ folder.pathAsFolder_linkId?.[i],
),
);
}
diff --git a/src/app/shared/services/data/data.service.ts b/src/app/shared/services/data/data.service.ts
index aa910f343..1dc41eb9a 100644
--- a/src/app/shared/services/data/data.service.ts
+++ b/src/app/shared/services/data/data.service.ts
@@ -58,6 +58,10 @@ export class DataService {
private thumbRefreshQueue: Array = [];
private thumbRefreshTimeout;
+ private _ephemeralFolder: FolderVO;
+
+ private _breadcrumbFolders: Array = [];
+
public multiclickItems: Map = new Map();
private selectedItems: SelectedItemsSet = new Set();
@@ -106,6 +110,30 @@ export class DataService {
}
}
+ public pushBreadcrumbFolder(folder: FolderVO) {
+ this._breadcrumbFolders.push(folder);
+ }
+
+ public cutBreadcrumbRoute(folder: FolderVO) {
+ const folderIndex = this._breadcrumbFolders.findIndex(
+ (currentFolder) => currentFolder.folderId === folder.folderId,
+ );
+ const foldersLength = this._breadcrumbFolders.length;
+ this._breadcrumbFolders.splice(folderIndex + 1, foldersLength);
+ }
+
+ get breadcrumbFolders() {
+ return this._breadcrumbFolders;
+ }
+
+ get ephemeralFolder() {
+ return this._ephemeralFolder;
+ }
+
+ set ephemeralFolder(folder: FolderVO) {
+ this._ephemeralFolder = folder;
+ }
+
public setCurrentFolder(folder?: FolderVO, isPage?: boolean) {
if (folder === this.currentFolder) {
return;