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
35 changes: 35 additions & 0 deletions config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,38 @@ rest:
host: sandbox.dspace.org
port: 443
nameSpace: /server

graph-viewer:
url: 'https://eltetanrep-graf.dspace.testing.qulto.eu/'

mediaViewer:
image: true
video: true

themes:
- name: elte
headTags:
- tagName: link
attributes:
rel: icon
href: /assets/qulto/images/qultoIcon.svg
sizes: image/svg+xml

search:
# Settings to enable/disable or configure Advanced Search filters.
advancedFilters:
enabled: true
# List of filters to enable in "Advanced Search" dropdown
filter: [ 'title', 'author', 'subject', 'entityType' ]


homePage:
topLevelCommunityList:
# No. of communities to list per page on the home page
# This will always round to the nearest number from the list of page sizes. e.g. if you set it to 7 it'll use 10
pageSize: 4
# layout can be "cards" or "list"
layout: "list"

item:
showAccessStatuses: true
7 changes: 7 additions & 0 deletions src/app/app-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
ERROR_PAGE,
FORBIDDEN_PATH,
FORGOT_PASSWORD_PATH,
GRAPH_VIEWER_PATH,
HEALTH_PAGE_PATH,
INFO_MODULE_PATH,
INTERNAL_SERVER_ERROR,
Expand Down Expand Up @@ -266,6 +267,12 @@ export const APP_ROUTES: Route[] = [
.then((m) => m.ROUTES),
canActivate: [authenticatedGuard],
},
{
path: GRAPH_VIEWER_PATH,
loadComponent: () => import('../themes/elte/app/graph-viewer/graph-viewer.component')
.then(m => m.GraphViewerComponent),
data: { title: 'graph-viewer.title' },
},
{ path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent },
],
},
Expand Down
1 change: 1 addition & 0 deletions src/app/app-routing-paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,4 @@ export function getEditItemPageRoute() {
}
export const CORRECTION_TYPE_PATH = 'corrections';

export const GRAPH_VIEWER_PATH = 'graph-viewer';
28 changes: 28 additions & 0 deletions src/themes/elte/app/graph-viewer/graph-viewer.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
:host {
display: block;
// Prevent the component from generating its own scrollbar
overflow: hidden;
}

.graph-viewer-container {
/* Calculate available height: 100vh minus (Header + Footer + potential margins) */
/* 160px is a safe estimate for the header and footer; adjust if necessary */
height: calc(100vh - 160px);
width: 100%;
display: flex;
flex-direction: column;
}

.graph-iframe {
flex-grow: 1;
width: 100%;
height: 100%;
border: none;
}

/* Overwrite DSpace default container padding/max-width to ensure full-width layout */
:host-context(ds-graph-viewer) .container {
max-width: 100% !important;
padding: 0 !important;
margin: 0 !important;
}
143 changes: 143 additions & 0 deletions src/themes/elte/app/graph-viewer/graph-viewer.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import {
AsyncPipe,
NgIf,
} from '@angular/common';
import {
Component,
HostListener,
Inject,
NgZone,
OnDestroy,
OnInit,
} from '@angular/core';
import {
DomSanitizer,
SafeResourceUrl,
} from '@angular/platform-browser';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { Subscription } from 'rxjs';
import {
APP_CONFIG,
AppConfig,
} from 'src/config/app-config.interface';

@Component({
selector: 'ds-graph-viewer',
standalone: true,
imports: [NgIf, AsyncPipe, TranslateModule],
template: `
<div class="graph-viewer-container">
<iframe *ngIf="safeUrl"
[src]="safeUrl"
class="graph-iframe"
frameborder="0">
</iframe>
</div>
`,
styleUrls: ['./graph-viewer.component.scss'],
})
export class GraphViewerComponent implements OnInit, OnDestroy {
safeUrl: SafeResourceUrl;
baseUrl: string;
private lastQuery: string | undefined = undefined; // Undefined value for the initial state
private subs: Subscription[] = [];

constructor(
@Inject(APP_CONFIG) protected appConfig: AppConfig,
private sanitizer: DomSanitizer,
private route: ActivatedRoute,
private router: Router,
private zone: NgZone,
) {}

ngOnInit(): void {
this.baseUrl = (this.appConfig as any)['graph-viewer']?.url;

if (this.baseUrl) {
this.subs.push(
this.route.queryParams.subscribe((params) => {
const currentQ = params.q || ''; // Normalize to empty string if missing

// Trigger build only if it's the first load OR an external change (e.g. Back button)
if (this.lastQuery === undefined || currentQ !== this.lastQuery) {
// console.log('Iframe source update required');
this.lastQuery = currentQ;
this.buildSafeUrl(currentQ);
}
}),
);
}
}

/**
* Builds the sanitized URL for the iframe.
* @param q The query value to append
*/
private buildSafeUrl(q?: string): void {
if (!this.baseUrl) {return;}

try {
const url = new URL(this.baseUrl);
const queryParam = q || this.route.snapshot.queryParams.q;

if (queryParam) {
url.searchParams.append('q', queryParam);
}

this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url.toString());
} catch (e) {
// console.error('Invalid URL format');
}
}

@HostListener('window:message', ['$event'])
onMessage(event: MessageEvent) {
if (!event.data || typeof event.data !== 'object' || !event.data.type) {return;}
if (this.baseUrl && !this.baseUrl.startsWith(event.origin)) {return;}

const { type, data } = event.data;

if (type === 'SEARCH_CHANGE' && data) {
let valueToNavigate = '';

// Handle different data structures from React
if (typeof data === 'object' && data.q && Array.isArray(data.q)) {
valueToNavigate = data.q[0];
} else if (typeof data === 'string') {
valueToNavigate = data;
}

if (valueToNavigate) {
// Block the next subscription-based reload
this.lastQuery = valueToNavigate;

this.zone.run(() => {
this.updateHostUrl(valueToNavigate);
});
}
}

if (type === 'OPEN_URL' && data) {
this.zone.run(() => {
window.open(data, '_blank');
});
}
}

private updateHostUrl(qValue: string): void {
this.router.navigate([], {
relativeTo: this.route,
queryParams: { q: qValue },
queryParamsHandling: 'merge',
replaceUrl: true,
});
}

ngOnDestroy(): void {
this.subs.forEach((s) => s.unsubscribe());
}
}
60 changes: 58 additions & 2 deletions src/themes/elte/app/navbar/navbar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,21 @@ import {
NgFor,
NgIf,
} from '@angular/common';
import { Component } from '@angular/core';
import {
Component,
inject,
OnDestroy,
OnInit,
} from '@angular/core';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core';
import { Subscription } from 'rxjs'; // Fontos import!
import { GRAPH_VIEWER_PATH } from 'src/app/app-routing-paths';
import { ThemedUserMenuComponent } from 'src/app/shared/auth-nav-menu/user-menu/themed-user-menu.component';
import { MenuService } from 'src/app/shared/menu/menu.service';
import { MenuID } from 'src/app/shared/menu/menu-id.model';
import { LinkMenuItemModel } from 'src/app/shared/menu/menu-item/models/link.model';
import { MenuItemType } from 'src/app/shared/menu/menu-item-type.model';

import { NavbarComponent as BaseComponent } from '../../../../app/navbar/navbar.component';
import { slideMobileNav } from '../../../../app/shared/animations/slide';
Expand All @@ -26,5 +37,50 @@ import { slideMobileNav } from '../../../../app/shared/animations/slide';
standalone: true,
imports: [NgbDropdownModule, NgClass, NgIf, ThemedUserMenuComponent, NgFor, NgComponentOutlet, AsyncPipe, TranslateModule],
})
export class NavbarComponent extends BaseComponent {
export class NavbarComponent extends BaseComponent implements OnInit, OnDestroy {
// ITT DEKLARÁLJUK a változót, így elűnik a hibaüzenet
private menuSubs: Subscription[] = [];

// A BaseComponent-től örökölt szervizek mellé injektáljuk, amit kell
protected menuService = inject(MenuService);

ngOnInit() {
super.ngOnInit();

// REAKTÍV FIGYELŐ:
// Feliratkozunk a menüpontok listájára. Ha a DSpace (pl. kereséskor)
// törli a listát, ez a kód azonnal észreveszi és visszateszi a miénket.
this.menuSubs.push(
this.menuService.getMenuTopSections(MenuID.PUBLIC).subscribe((sections) => {
const exists = sections.some(section => section.id === 'elte_graph_viewer');
if (!exists) {
this.addGraphMenu();
}
}),
);
}

private addGraphMenu() {
this.menuService.addSection(MenuID.PUBLIC, {
id: 'elte_graph_viewer',
active: true,
visible: true,
model: {
type: MenuItemType.LINK,
text: 'menu.section.graph-viewer',
link: '/' + GRAPH_VIEWER_PATH,
} as LinkMenuItemModel,
icon: 'network-wired',
index: 10,
});
}

ngOnDestroy() {
// Lezárjuk a figyelőt, amikor elhagyjuk az oldalt (memóriaszivárgás ellen)
this.menuSubs.forEach(sub => sub.unsubscribe());

if (super.ngOnDestroy) {
super.ngOnDestroy();
}
}
}
4 changes: 4 additions & 0 deletions src/themes/elte/assets/i18n/en.json5
Original file line number Diff line number Diff line change
Expand Up @@ -452,4 +452,8 @@
"footer.instituteName": "Eötvös Loránd University",

"footer.rrf": "The project ID RRF-2.1.2-21-2022-00023, titled 'Infrastructural and skills development of practice-oriented higher education programs', was implemented within the framework of Hungary’s Recovery and Resilience Plan.",

"graph-viewer.title": "Graph Viewer",

"menu.section.graph-viewer": "Graph Viewer",
}
8 changes: 6 additions & 2 deletions src/themes/elte/assets/i18n/hu.json5
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
"relationships.isCourseOfLearningObject": "Tantárgyak",

// "relationships.isCourseInstanceOfLearningObject": "Course Instances",
""relationships.isCourseInstanceOfLearningObject": "Kurzusok",
"relationships.isCourseInstanceOfLearningObject": "Kurzusok",

// "relationships.isTypeOfLearningObject": "Type",
"relationships.isTypeOfLearningObject": "Típus",
Expand Down Expand Up @@ -472,6 +472,8 @@

"menu.section.browse_global_by_course": "Tantárgy szerint",

"menu.section.graph-viewer": "Gráf megjelenítő",

"browse.metadata.program": "Képzés",

"browse.metadata.program.breadcrumbs": "Böngészés képzés szerint",
Expand All @@ -496,5 +498,7 @@

"footer.instituteName": "Eötvös Loránd Tudományegyetem",

"footer.rrf": "Az RRF-2.1.2-21-2022-00023 azonosító számon nyilvántartott „Gyakorlatorientált felsőfokú képzések infrastrukturális- és készségfejlesztése” projekt Magyarország Helyreállítási és Ellenállóképességi Tervének keretében valósult meg."
"footer.rrf": "Az RRF-2.1.2-21-2022-00023 azonosító számon nyilvántartott „Gyakorlatorientált felsőfokú képzések infrastrukturális- és készségfejlesztése” projekt Magyarország Helyreállítási és Ellenállóképességi Tervének keretében valósult meg.",

"graph-viewer.title": "Gráf megjelenítő",
}
6 changes: 3 additions & 3 deletions src/themes/elte/eager-theme.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ import {
} from 'src/app/shared/metadata-representation/metadata-representation.decorator';

import { RootModule } from '../../app/root.module';
import { GenericItemMetadataListElementComponent } from './app/entity-groups/research-entities/metadata-representations/generic-item/generic-item-metadata-list-element.component';
// Your themed components
import { PersonComponent } from './app/entity-groups/research-entities/item-pages/person/person.component';
import { GenericItemMetadataListElementComponent } from './app/entity-groups/research-entities/metadata-representations/generic-item/generic-item-metadata-list-element.component';
import { FooterComponent } from './app/footer/footer.component';
import { HeaderComponent } from './app/header/header.component';
import { HeaderNavbarWrapperComponent } from './app/header-nav-wrapper/header-navbar-wrapper.component';
import { HomeNewsComponent } from './app/home-page/home-news/home-news.component';
import { ElteRelatedItemsComponent } from './app/item-page/simple/elte-related-items/elte-related-items.component';
import { LearningObjectComponent } from './app/item-page/simple/item-types/learning-object/learning-object.component';
import { MetadataValuesComponent } from './app/item-page/simple/field-components/specific-field/metadata-values/metadata-values.component';
import { CourseInstanceComponent } from './app/item-page/simple/item-types/course-instance/course-instance.component';
import { LearningObjectComponent } from './app/item-page/simple/item-types/learning-object/learning-object.component';
import { PublicationComponent } from './app/item-page/simple/item-types/publication/publication.component';
import { SimpleItemComponent } from './app/item-page/simple/item-types/simple-item/simple-item.component';
import { NavbarComponent } from './app/navbar/navbar.component';
import { LangSwitchComponent } from './app/shared/lang-switch/lang-switch.component';
import { CommunityListElementComponent } from './app/shared/object-list/community-list-element/community-list-element.component';
import { MetadataValuesComponent } from './app/item-page/simple/field-components/specific-field/metadata-values/metadata-values.component';

const THEME = 'elte';

Expand Down
Loading