Skip to content
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
128 changes: 70 additions & 58 deletions src/app/components/actionbar/actionbar.component.html
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
<mat-toolbar id="actionbar">
@if (element(); as element) {
<!-- Publishing option -->
@if (isAuthenticated() && isEditingAllowed() && isEntity() && isDetailPage()) {
@if (isAuthenticated() && isEditingAllowed() && isDetailPage()) {
<div>
@if (isPublished()) {
<button
mat-flat-button
color="primary"
matTooltip="{{
'This object is currently open to the public. Click to unpublish!' | translate
}}"
[disabled]="!(element | isUserOfRole: 'owner' : user())"
[matTooltip]="'Published. Click to unpublish!' | translate"
(click)="togglePublished()"
>
<mat-icon>flip_to_back</mat-icon>
Expand All @@ -20,10 +17,7 @@
<button
mat-flat-button
color="primary"
matTooltip="{{
'This object is currently hidden from the public. Click to publish!' | translate
}}"
[disabled]="!(element | isUserOfRole: 'owner' : user())"
[matTooltip]="'Not public. Click to publish!' | translate"
(click)="togglePublished()"
>
<mat-icon>publish</mat-icon>
Expand All @@ -44,9 +38,8 @@
} @else if (isDetailPage()) {
<!-- Switch to annotation page -->
<div
[matTooltip]="
(isAnnotatingAllowed() ? '' : 'You are not allowed to annotate right now.') | translate
"
[matTooltipDisabled]="isAnnotatingAllowed()"
[matTooltip]="'You are not allowed to annotate right now.' | translate"
>
<button
mat-flat-button
Expand Down Expand Up @@ -130,50 +123,58 @@
}

<div>
<button
mat-icon-button
matTooltip="{{ 'Embed this object on your web page' | translate }}"
(click)="copyEmbed()"
>
<mat-icon>perm_media</mat-icon>
<button mat-icon-button [matMenuTriggerFor]="sharingOptionsMenu">
<mat-icon>share</mat-icon>
</button>
</div>
@if (metadataDownload(); as metadataDownload) {
<a
[href]="metadataDownload.url"
[download]="metadataDownload.title"
style="color: inherit"
class="no-prefix"
>
<button mat-icon-button matTooltip="{{ 'Download metadata' | translate }}">
<mat-icon>list_alt</mat-icon>
</button>
</a>
}
<div>
<button mat-icon-button matTooltip="{{ 'Copy ID' | translate }}" (click)="copyId()">
<mat-icon>content_copy</mat-icon>
}
</mat-toolbar>

<mat-menu #sharingOptionsMenu="matMenu">
<!-- Download model files -->
@if (isEntity()) {
<div
matTooltipPosition="before"
[matTooltipDisabled]="isDownloadable()"
[matTooltip]="'Download not available' | translate"
>
<button mat-menu-item (click)="openDownloadDialog()" [disabled]="!isDownloadable()">
<mat-icon color="primary" class="material-symbols-filled">download</mat-icon>
{{ 'Download model' | translate }}
</button>
</div>

<!-- Download model files -->
@if (isEntity()) {
@if (isDownloadable()) {
<div matTooltip="{{ 'Download model files' | translate }}">
<button mat-icon-button (click)="openDownloadDialog()" [disabled]="!isDownloadable()">
<mat-icon>file_download</mat-icon>
</button>
</div>
}
@if (metadataDownload(); as metadataDownload) {
<a
[href]="metadataDownload.url"
[download]="metadataDownload.title"
style="color: inherit; text-decoration: none"
class="no-prefix"
>
<button mat-menu-item>
<mat-icon color="primary" class="material-symbols-filled">download</mat-icon>
{{ 'Download metadata' | translate }}
</button>
</a>
}
@if (element(); as element) {
<button mat-menu-item (click)="copyEmbed()">
<mat-icon color="primary" class="material-symbols-filled">code_xml</mat-icon>
@if (element | isEntity) {
{{ 'Embed model' | translate }}
} @else {
<div matTooltip="{{ 'Download not available' | translate }}">
<button mat-icon-button disabled>
<mat-icon>file_download</mat-icon>
</button>
</div>
{{ 'Embed collection' | translate }}
}
}
</button>
}
</mat-toolbar>

<div matTooltipPosition="before" [matTooltip]="'Coming soon' | translate">
<button mat-menu-item disabled>
<mat-icon color="primary" class="material-symbols-filled">format_quote</mat-icon>
{{ 'Cite' | translate }}
</button>
</div>
</mat-menu>

<mat-menu #usedInCompilationsMenu="matMenu">
@for (compilation of selectHistory.usedInCompilations.compilations; track compilation._id) {
Expand Down Expand Up @@ -202,16 +203,27 @@
@if (element(); as element) {
@if (element | isEntity) {
<button mat-menu-item (click)="editSettingsInViewer()">
{{ 'Viewer settings' | translate }}
<mat-icon color="primary">settings</mat-icon>
<span>{{ 'Edit 3D settings' | translate }}</span>
</button>
}
@if (element | isUserOfRole: ['owner', 'editor'] : user()) {
<button mat-menu-item (click)="editMetadata()">
<mat-icon color="primary">edit</mat-icon>
<span>{{ 'Edit metadata' | translate }}</span>
</button>
}
@if (element | isUserOfRole: 'owner' : user()) {
<button mat-menu-item (click)="editVisibility()">
<mat-icon color="primary">visibility</mat-icon>
<span>{{ 'Visibility and access' | translate }}</span>
</button>
}
@if (isFinished()) {
<button mat-menu-item (click)="openTransferOwnerDialog()">
<mat-icon color="primary">manage_accounts</mat-icon>
<span>{{ 'Transfer ownership' | translate }}</span>
</button>
<button mat-menu-item (click)="editMetadata()">{{ 'Metadata' | translate }}</button>
}
<button
mat-menu-item
[disabled]="!(element | isUserOfRole: 'owner' : user())"
(click)="editVisibility()"
>
{{ 'Visibility and access' | translate }}
</button>
}
</mat-menu>
90 changes: 59 additions & 31 deletions src/app/components/actionbar/actionbar.component.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import { combineLatest, filter, firstValueFrom, map, of, shareReplay, switchMap, tap } from 'rxjs';
import {
filter,
firstValueFrom,
map,
of,
shareReplay,
switchMap,
tap,
} from 'rxjs';

import { Component, computed, input, signal } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatSelectModule } from '@angular/material/select';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { Router, RouterLink } from '@angular/router';

import { MatButtonModule } from '@angular/material/button';
import { MatOptionModule } from '@angular/material/core';
Expand Down Expand Up @@ -78,6 +86,14 @@ export class ActionbarComponent {
});
isEntity = computed(() => isEntity(this.element()));
isCompilation = computed(() => isCompilation(this.element()));
isFinished = computed(() => {
const element = this.element();
if (isEntity(element)) {
return element.finished;
} else if (isCompilation(element)) {
return Object.keys(element.entities).length > 0;
}
});

showUsesInCollection = computed(() => this.isEntity() && this.isDetailPage());

Expand Down Expand Up @@ -131,7 +147,6 @@ export class ActionbarComponent {
private detailPageHelper: DetailPageHelperService,
private dialogHelper: DialogHelperService,
private router: Router,
private activatedRoute: ActivatedRoute,
private sanitizer: DomSanitizer,
public selectHistory: SelectHistoryService,
private snackbar: SnackbarService,
Expand Down Expand Up @@ -204,9 +219,9 @@ export class ActionbarComponent {
isCompilation: isCompilation(element),
});
if (!element) return of(false);
return combineLatest([
this.allowAnnotatingHelper.isUserOwner$(element),
this.allowAnnotatingHelper.userHasAccess$(element, EntityAccessRole.editor),
return this.allowAnnotatingHelper.userHasAccess$(element, [
EntityAccessRole.editor,
EntityAccessRole.owner,
]);
}),
tap(isAllowed => console.log('isAnnotatingAllowed$ isAllowed', isAllowed)),
Expand All @@ -215,7 +230,12 @@ export class ActionbarComponent {

#isEditingAllowed$ = this.element$.pipe(
filter(element => !!element),
switchMap(element => this.allowAnnotatingHelper.isUserOwner$(element)),
switchMap(element =>
this.allowAnnotatingHelper.userHasAccess$(element, [
EntityAccessRole.editor,
EntityAccessRole.owner,
]),
),
);
isEditingAllowed = toSignal(this.#isEditingAllowed$);

Expand Down Expand Up @@ -258,15 +278,18 @@ export class ActionbarComponent {

public editMetadata() {
const element = this.element();
if (!isEntity(element)) return;
this.dialogHelper.editEntity(element);
if (isEntity(element)) {
return this.dialogHelper.editEntity(element);
} else if (isCompilation(element)) {
return this.dialogHelper.createOrEditCompilation(element);
}
}

public editVisibility() {
const element = this.element();
if (!isEntity(element)) return;
if (!element) return;

const ref = this.dialogHelper.editVisibilityAndAccess([element]);
const ref = this.dialogHelper.editVisibilityAndAccess([element] as IEntity[] | ICompilation[]);

ref.afterClosed().subscribe(result => {
if (!result) return;
Expand All @@ -278,22 +301,36 @@ export class ActionbarComponent {
});
}

public openTransferOwnerDialog() {
const element = this.element();
if (!element) return;
return this.dialogHelper.openTransferOwnershipDialog(element);
}

isPublished = computed(() => {
const element = this.element();
if (!isEntity(element)) return false;
return !!element.online;
return element && 'online' in element ? !!element.online : false;
});

public async togglePublished() {
const element = this.element();
if (!isEntity(element)) return;
this.backend
.pushEntity({ ...element, online: !element.online })
.then(result => {
console.log('Toggled?:', result);
if (isEntity(result)) this.updatedElement.set(result);
})
.catch(error => console.error(error));
if (isEntity(element)) {
this.backend
.pushEntity({ ...element, online: !element.online })
.then(result => {
console.log('Toggled?:', result);
if (isEntity(result)) this.updatedElement.set(result);
})
.catch(error => console.error(error));
} else if (isCompilation(element)) {
this.backend
.pushCompilation({ ...element, online: !element.online })
.then(result => {
console.log('Toggled?:', result);
if (isCompilation(result)) this.updatedElement.set(result);
})
.catch(error => console.error(error));
}
}

public copyEmbed() {
Expand Down Expand Up @@ -324,15 +361,6 @@ export class ActionbarComponent {
this.detailPageHelper.copyEmbed(embedHTML);
}

public copyId() {
const element = this.element();
if (!element) return this.snackbar.showMessage('Could not find element');
const _id = element?._id;
if (!_id) return this.snackbar.showMessage('Could not copy id');

this.detailPageHelper.copyID(_id.toString() ?? '');
}

public async openDownloadDialog() {
const options = await firstValueFrom(this.entityDownloadOptions$);
if (!options) {
Expand All @@ -351,6 +379,6 @@ export class ActionbarComponent {
return;
}

this.dialogHelper.openEntityDownloadDialog(element, options);
return this.dialogHelper.openEntityDownloadDialog(element, options);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Inject } from '@angular/core';
import { Component, inject, Inject } from '@angular/core';
import {
MAT_DIALOG_DATA,
MatDialogRef,
Expand All @@ -8,12 +8,12 @@ import {
import { TranslatePipe } from '../../pipes/translate.pipe';
import { MatButtonModule } from '@angular/material/button';

export interface IConfirmationDialogData {
title?: string;
message: string;
}

export type ConfirmationDialogData = string | IConfirmationDialogData;
export type ConfirmationDialogData =
| string
| {
title?: string;
message: string;
};

@Component({
selector: 'app-confirmation-dialog',
Expand All @@ -22,10 +22,8 @@ export type ConfirmationDialogData = string | IConfirmationDialogData;
imports: [MatDialogContent, MatButtonModule, MatDialogClose, TranslatePipe],
})
export class ConfirmationDialogComponent {
constructor(
public dialogRef: MatDialogRef<ConfirmationDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: ConfirmationDialogData,
) {}
dialogRef = inject(MatDialogRef<ConfirmationDialogComponent>);
data = inject(MAT_DIALOG_DATA) as ConfirmationDialogData;

get title() {
if (typeof this.data === 'string') return undefined;
Expand Down
14 changes: 2 additions & 12 deletions src/app/pages/profile-page/entities/entities.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,18 +219,8 @@ export class ProfileEntitiesComponent implements AfterViewInit {
public openTransferOwnerDialog(entity?: IEntity) {
const selection = this.selectionService().selectedElements();
const data = entity ?? (selection.length === 1 ? selection[0] : selection);

const dialogRef = this.dialog.open(ManageOwnershipComponent, {
data: data,
disableClose: false,
});

dialogRef
.afterClosed()
.toPromise()
.then(result => {
this.account.updateTrigger$.next(Collection.entity);
});
if (!isEntity(data)) return;
this.helper.openTransferOwnershipDialog(data);
}

public openVisibilityAndAccessDialog(entity?: IEntity) {
Expand Down
Loading
Loading