Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -125,26 +125,38 @@
></pr-inline-value-edit>
}
</div>
@if (showEdtfDatePicker) {
<div class="file-viewer-date">
<pr-sidebar-date-picker
[displayTime]="displayTimeObject"
[disabled]="!canEdit"
(saveClicked)="onDateSaved($event)"
(moreOptionsClicked)="onDateMoreOptions($event)"
/>
</div>
}
<table class="metadata-table">
<tr>
<td>Date</td>
<td>
<pr-inline-value-edit
[displayValue]="
currentRecord.displayTime || currentRecord.displayDT
"
[canEdit]="canEdit"
[item]="currentRecord"
[itemId]="currentRecord.folder_linkId"
emptyMessage="Click to add date"
readOnlyEmptyMessage="No date"
(doneEditing)="onFinishEditing('displayTime', $event)"
(toggledDatePicker)="onDateToggle($event)"
type="date"
class="no-padding"
></pr-inline-value-edit>
</td>
</tr>
@if (!showEdtfDatePicker) {
<tr>
<td>Date</td>
<td>
<pr-inline-value-edit
[displayValue]="
currentRecord.displayTime || currentRecord.displayDT
"
[canEdit]="canEdit"
[item]="currentRecord"
[itemId]="currentRecord.folder_linkId"
emptyMessage="Click to add date"
readOnlyEmptyMessage="No date"
(doneEditing)="onFinishEditing('displayTime', $event)"
(toggledDatePicker)="onDateToggle($event)"
type="date"
class="no-padding"
></pr-inline-value-edit>
</td>
</tr>
}
<tr>
<td>Uploaded</td>
<td>{{ currentRecord.createdDT | date }}</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ import { ApiService } from '@shared/services/api/api.service';
import { FeatureFlagService } from '@root/app/feature-flag/services/feature-flag.service';
import { MockComponent } from 'ng-mocks';
import { GetThumbnailPipe } from '@shared/pipes/get-thumbnail.pipe';
import { MessageService } from '@shared/services/message/message.service';
import {
DateTimeModel,
EdtfService,
} from '@shared/services/edtf-service/edtf.service';
import { TagsComponent } from '../../../shared/components/tags/tags.component';
import { EditDateTimeModalService } from '../edit-date-time-modal/edit-date-time-modal.service';
import { FileViewerComponent } from './file-viewer.component';

@Pipe({ name: 'dsFileSize', standalone: false })
Expand Down Expand Up @@ -229,6 +235,19 @@ describe('FileViewerComponent', () => {
isEnabled: (flag: string) => featureFlagsEnabled.get(flag) ?? false,
},
},
{
provide: MessageService,
useValue: {
showError: () => {},
showMessage: () => {},
},
},
{
provide: EditDateTimeModalService,
useValue: {
open: () => ({ closed: { subscribe: () => {} } }),
},
},
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
}).compileComponents();
Expand All @@ -244,6 +263,68 @@ describe('FileViewerComponent', () => {
expect(component).not.toBeNull();
});

describe('edtf-date feature flag', () => {
it('should show the EDTF date picker when the edtf-date flag is enabled', async () => {
featureFlagsEnabled.set('edtf-date', true);
await recreateComponent();

expect(component.showEdtfDatePicker).toBe(true);
expect(
fixture.nativeElement.querySelector('pr-sidebar-date-picker'),
).toBeTruthy();
});

it('should show the legacy date field and hide the EDTF picker when the edtf-date flag is disabled', async () => {
featureFlagsEnabled.set('edtf-date', false);
await recreateComponent();

expect(component.showEdtfDatePicker).toBe(false);
expect(
fixture.nativeElement.querySelector('pr-sidebar-date-picker'),
).toBeNull();

const dateRowLabel = Array.from(
fixture.nativeElement.querySelectorAll('.metadata-table td'),
).find((td: HTMLElement) => td.textContent?.trim() === 'Date');

expect(dateRowLabel).toBeTruthy();
});
});

describe('EDTF date handling', () => {
const recordWithDate = () =>
new RecordVO({
type: 'document',
displayName: 'Dated Doc',
TagVOs: [],
displayTime: '1985-05-20',
});

it('should compute the cached display time from the record on init', async () => {
activatedRouteData.currentRecord = recordWithDate();
await recreateComponent();

expect(component.displayTimeObject?.date.year).toBe('1985');
});

it('should reset the cached display time and show one error when an invalid date is saved', async () => {
activatedRouteData.currentRecord = recordWithDate();
await recreateComponent();

const edtfService = TestBed.inject(EdtfService);
spyOn(edtfService, 'toEdtfDate').and.throwError('invalid date');
const showErrorSpy = spyOn(TestBed.inject(MessageService), 'showError');

await component.onDateSaved({
date: { year: 'bad' } as never,
time: { format: 'am' },
} as DateTimeModel);

expect(showErrorSpy).toHaveBeenCalledTimes(1);
expect(component.displayTimeObject?.date.year).toBe('1985');
});
});

it('should have two tags components', () => {
const tagsComponents = fixture.nativeElement.querySelectorAll('pr-tags');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ import { GetAccessFile } from '@models/get-access-file';
import { ShareLinksService } from '@root/app/share-links/services/share-links.service';
import { ApiService } from '@shared/services/api/api.service';
import { FeatureFlagService } from '@root/app/feature-flag/services/feature-flag.service';
import {
DateTimeModel,
EdtfService,
} from '@shared/services/edtf-service/edtf.service';
import { MessageService } from '@shared/services/message/message.service';
import { TagsService } from '../../../core/services/tags/tags.service';
import { EditDateTimeModalService } from '../edit-date-time-modal/edit-date-time-modal.service';

@Component({
selector: 'pr-file-viewer',
Expand Down Expand Up @@ -62,6 +68,12 @@ export class FileViewerComponent implements OnInit, OnDestroy {

public canEdit: boolean;

public showEdtfDatePicker = false;

public editingDate: boolean = false;

public displayTimeObject: DateTimeModel | null = null;

// Swiping
private touchElement: HTMLElement;
private thumbElement: HTMLElement;
Expand All @@ -77,17 +89,18 @@ export class FileViewerComponent implements OnInit, OnDestroy {

// UI
public useMinimalView = false;
public editingDate: boolean = false;
private bodyScrollTop: number;
private itemTagsSubscription: Subscription;
private tagsSubscription: Subscription;
private dateModalSubscription?: Subscription;
private isUnlistedShare = true;

constructor(
private router: Router,
private route: ActivatedRoute,
private element: ElementRef,
private dataService: DataService,
private message: MessageService,
@Inject(DOCUMENT) private document: any,
public sanitizer: DomSanitizer,
private accountService: AccountService,
Expand All @@ -97,10 +110,14 @@ export class FileViewerComponent implements OnInit, OnDestroy {
private shareLinksService: ShareLinksService,
private api: ApiService,
private feature: FeatureFlagService,
private edtfService: EdtfService,
private editDateTimeModalService: EditDateTimeModalService,
) {
// store current scroll position in file list
this.bodyScrollTop = window.scrollY;

this.showEdtfDatePicker = this.feature.isEnabled('edtf-date');

const resolvedRecord = route.snapshot.data.currentRecord;
this.allTags = tagsService.getTags();

Expand Down Expand Up @@ -186,6 +203,7 @@ export class FileViewerComponent implements OnInit, OnDestroy {
});
this.itemTagsSubscription.unsubscribe();
this.tagsSubscription.unsubscribe();
this.dateModalSubscription?.unsubscribe();
}

private setRecordsToPreview(resolvedRecord: RecordVO) {
Expand Down Expand Up @@ -244,6 +262,7 @@ export class FileViewerComponent implements OnInit, OnDestroy {
this.replayUrl = this.getReplayUrl();
}
this.setCurrentTags();
this.updateDisplayTimeObject();
}

toggleSwipe(value: boolean) {
Expand Down Expand Up @@ -432,6 +451,50 @@ export class FileViewerComponent implements OnInit, OnDestroy {
}
}

private updateDisplayTimeObject(): void {
const timeSource =
this.currentRecord?.displayTime || this.currentRecord?.displayDT;
try {
this.displayTimeObject = timeSource
? this.edtfService.toDateTimeModel(timeSource)
: null;
} catch (err) {
this.displayTimeObject = null;
this.message.showError({ message: err?.message });
}
}

public async onDateSaved(result: DateTimeModel): Promise<void> {
await this.saveDisplayTime(result);
}

public async onDateMoreOptions(modalData: DateTimeModel): Promise<void> {
const dialogRef = this.editDateTimeModalService.open(modalData);

this.dateModalSubscription = dialogRef.closed.subscribe(async (result) => {
if (result) {
await this.saveDisplayTime(result);
}
});
}

private async saveDisplayTime(result: DateTimeModel): Promise<void> {
try {
const newDisplayTime = this.edtfService.toEdtfDate(result);
await this.onFinishEditing('displayTime', newDisplayTime);
} catch (err) {
this.message.showError({ message: err?.message });
} finally {
// Recompute so the picker re-syncs to the stored value, whether the
// save came from the inline picker or the modal, and on failure too.
this.updateDisplayTimeObject();
}
}

public onDateToggle(active: boolean): void {
this.editingDate = active;
}

public async onFinishEditing(
property: KeysOfType<ItemVO, string>,
value: string,
Expand All @@ -455,10 +518,6 @@ export class FileViewerComponent implements OnInit, OnDestroy {
}
}

public onDateToggle(active: boolean): void {
this.editingDate = active;
}

public onDownloadClick(): void {
this.dataService.downloadFile(this.currentRecord);
}
Expand Down
5 changes: 4 additions & 1 deletion src/app/shared/components/message/message.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ $transition-length: 0.33s;
left: 0;
right: 0;
transform: translateY(-100%);
z-index: 6;
// Must sit above full-screen components (z-index 10, e.g. the file viewer)
// and the date/time picker dropdowns (z-index 20) that open within them,
// otherwise the error banner renders but is hidden behind the overlay.
z-index: 30;
}

.alert {
Expand Down