diff --git a/README.md b/README.md index e42dcf5..9b9f929 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,19 @@ npm start ## Usage Reactome has a wide range of features, to explore more of Reactome or get more information visit [the documentation page](https://reactome.org/documentation) or see the ````/documentation```` folder in the root directory. +## Configuration + +The application configuration is centralized in TypeScript files under `projects/website-angular/src/config/`. Key configurations include: + +- `config.ts`: App-level settings like version, base URLs, and feature flags. +- `environments.ts`: Environment-specific settings (development, production, etc.). +- `api-routes.ts`: API endpoint URLs derived from the current environment. +- `features.ts`: Feature flags for toggling functionality. +- `external-links.ts`: External links, including dynamically constructed release notes. + +To update configuration values, edit the respective TS files. For environment-specific builds, consider using Angular's file replacements in `angular.json`. + +## Additional Resources ## LICENSE [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) diff --git a/projects/pathway-browser/src/environments/environment.development.ts b/projects/pathway-browser/src/environments/environment.development.ts index aacd34d..9bacf86 100644 --- a/projects/pathway-browser/src/environments/environment.development.ts +++ b/projects/pathway-browser/src/environments/environment.development.ts @@ -1,10 +1,14 @@ +import { ENVIRONMENTS } from '../../../website-angular/src/config/environments'; + +const env = ENVIRONMENTS.development; + export const environment = { production: true, - host: "../..", // For go back from /beta/PathwayBrowser - s3: "https://download.reactome.org", - gsaServer: "dev", - gtagId: "G-96F1EYHQR3", - preferS3: false, + host: env.host, + s3: env.s3, + gsaServer: env.gsaServer, + gtagId: env.gtagId, + preferS3: env.preferS3 }; export const CONTENT_SERVICE = `${environment.host}/ContentService`; diff --git a/projects/pathway-browser/src/environments/environment.production.ts b/projects/pathway-browser/src/environments/environment.production.ts index 327149f..688863e 100644 --- a/projects/pathway-browser/src/environments/environment.production.ts +++ b/projects/pathway-browser/src/environments/environment.production.ts @@ -1,11 +1,14 @@ +import { ENVIRONMENTS } from '../../../website-angular/src/config/environments'; + +const env = ENVIRONMENTS.production; + export const environment = { production: true, - host: "../..", // For go back from /beta/PathwayBrowser - s3: "https://download.reactome.org", - gsaServer: "production", - gtagId: "G-EDHZ92GXZP", - preferS3: true, - + host: env.host, + s3: env.s3, + gsaServer: env.gsaServer, + gtagId: env.gtagId, + preferS3: env.preferS3 }; export const CONTENT_SERVICE = `${environment.host}/ContentService`; diff --git a/projects/website-angular/src/app/copyright-footer/copyright-footer.component.ts b/projects/website-angular/src/app/copyright-footer/copyright-footer.component.ts index 0109461..b4ab62f 100644 --- a/projects/website-angular/src/app/copyright-footer/copyright-footer.component.ts +++ b/projects/website-angular/src/app/copyright-footer/copyright-footer.component.ts @@ -3,6 +3,7 @@ import { CommonModule } from '@angular/common'; import { MatIcon } from "@angular/material/icon"; import {NavLink} from '../../types/link'; import { mapNavOptions } from '../../utils/nav-options-mapper'; +import { EXTERNAL_LINKS } from '../../config/external-links'; // NEW import @Component({ selector: 'app-copyright-footer', @@ -19,9 +20,7 @@ export class copyrightFooterComponent implements OnInit { } loadExternalLinks() { - // Load external links from the JSON file - import('../../config/external-links.json').then((data) => { - this.externalLinks = mapNavOptions(data.default); - }); + // Use the new TS constant instead of dynamic JSON import + this.externalLinks = EXTERNAL_LINKS as unknown as Record; } } diff --git a/projects/website-angular/src/app/home-page/home-help/home-help.component.ts b/projects/website-angular/src/app/home-page/home-help/home-help.component.ts index 86a3a97..0ac2b90 100644 --- a/projects/website-angular/src/app/home-page/home-help/home-help.component.ts +++ b/projects/website-angular/src/app/home-page/home-help/home-help.component.ts @@ -1,9 +1,10 @@ import { Component } from '@angular/core'; -import { CarouselComponent } from "../../reactome-components/carousel/carousel.component"; -import { ButtonComponent } from "../../reactome-components/button/button.component"; -import { MatIcon } from "@angular/material/icon"; +import { CarouselComponent } from '../../reactome-components/carousel/carousel.component'; +import { ButtonComponent } from '../../reactome-components/button/button.component'; +import { MatIcon } from '@angular/material/icon'; import { mapNavOptions } from '../../../utils/nav-options-mapper'; import { ExternalLink, NavOption } from '../../../types/link'; +import { EXTERNAL_LINKS } from '../../../config/external-links'; // NEW import @Component({ selector: 'app-home-help', @@ -31,10 +32,9 @@ export class HomeHelpComponent { } loadExternalLinks() { - import('../../../config/external-links.json').then((data) => { - this.externalLinks = mapNavOptions(data.default); - this.releaseNotesLink = this.externalLinks['releaseNotes']?.link || ''; - this.feedbackLink = this.externalLinks['feedback']?.link || ''; - }); + // Use the new TS constant instead of dynamic JSON import + this.externalLinks = EXTERNAL_LINKS as unknown as Record; + this.releaseNotesLink = this.externalLinks['releaseNotes']?.link || ''; + this.feedbackLink = this.externalLinks['feedback']?.link || ''; } } diff --git a/projects/website-angular/src/app/home-page/home-related/home-related.component.ts b/projects/website-angular/src/app/home-page/home-related/home-related.component.ts index 046c9c3..f93e096 100644 --- a/projects/website-angular/src/app/home-page/home-related/home-related.component.ts +++ b/projects/website-angular/src/app/home-page/home-related/home-related.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import {ExternalLink} from '../../../types/link'; import { mapNavOptions } from '../../../utils/nav-options-mapper'; +import { EXTERNAL_LINKS } from '../../../config/external-links'; // NEW import @Component({ selector: 'app-home-related', @@ -17,8 +18,7 @@ export class HomeRelatedComponent { } loadExternalLinks() { - import('../../../config/external-links.json').then((data) => { - this.externalLinks = mapNavOptions(data.default); - }); + // Use the new TS constant instead of dynamic JSON import + this.externalLinks = EXTERNAL_LINKS as unknown as Record; } } diff --git a/projects/website-angular/src/app/home-page/home-stats/home-stats.component.ts b/projects/website-angular/src/app/home-page/home-stats/home-stats.component.ts index 6dde1e9..43ca84d 100644 --- a/projects/website-angular/src/app/home-page/home-stats/home-stats.component.ts +++ b/projects/website-angular/src/app/home-page/home-stats/home-stats.component.ts @@ -2,6 +2,7 @@ import { Component, inject } from '@angular/core'; import { MatIcon } from "@angular/material/icon"; import { CarouselComponent } from "../../reactome-components/carousel/carousel.component"; import { StatsService } from '../../../services/stats.service'; +import { APP_CONFIG } from '../../../config/config'; // NEW import interface Stats { human_pathways: number; @@ -39,11 +40,10 @@ export class HomeStatsComponent { } getVersionAndDate () { - import('../../../config/config.json').then((data) => { - this.version = data.default.version.releaseNumber; - this.releaseDate = new Date(data.default.version.releaseDate); - this.fetchStats(); - }); + // Use APP_CONFIG instead of dynamic JSON import + this.version = APP_CONFIG.version.releaseNumber; + this.releaseDate = new Date(APP_CONFIG.version.releaseDate); + this.fetchStats(); } fetchStats () { diff --git a/projects/website-angular/src/app/home-page/home-why-reactome/home-why-reactome.component.ts b/projects/website-angular/src/app/home-page/home-why-reactome/home-why-reactome.component.ts index 5b86cd9..f4ecb54 100644 --- a/projects/website-angular/src/app/home-page/home-why-reactome/home-why-reactome.component.ts +++ b/projects/website-angular/src/app/home-page/home-why-reactome/home-why-reactome.component.ts @@ -1,6 +1,7 @@ import { Component } from '@angular/core'; import {ExternalLink, NavOption} from '../../../types/link'; import { mapNavOptions } from '../../../utils/nav-options-mapper'; +import { EXTERNAL_LINKS } from '../../../config/external-links'; // NEW import @Component({ selector: 'app-home-why-reactome', @@ -19,9 +20,8 @@ export class HomeWhyReactomeComponent { } loadExternalLinks() { - import('../../../config/external-links.json').then((data) => { - this.externalLinks = mapNavOptions(data.default); - }); + // Use the new TS constant instead of dynamic JSON import + this.externalLinks = EXTERNAL_LINKS as unknown as Record; } loadNavOptions() { diff --git a/projects/website-angular/src/app/navigation-bar/navigation-bar.component.ts b/projects/website-angular/src/app/navigation-bar/navigation-bar.component.ts index 39715fc..0026a6e 100644 --- a/projects/website-angular/src/app/navigation-bar/navigation-bar.component.ts +++ b/projects/website-angular/src/app/navigation-bar/navigation-bar.component.ts @@ -7,6 +7,7 @@ import { FormsModule } from '@angular/forms'; import { mapNavOptions } from '../../utils/nav-options-mapper'; import { NavLink, NavOption } from '../../types/link'; import { DarkService } from '../../../../pathway-browser/src/app/services/dark.service'; +import { APP_CONFIG } from '../../config/config'; // NEW import @Component({ standalone: true, @@ -43,12 +44,9 @@ export class NavigationBarComponent implements OnInit, AfterViewInit { } loadNavOptions() { - Promise.all([ - import('../../config/nav-options.json'), - import('../../config/config.json') - ]).then(([navData, configData]) => { + import('../../config/nav-options.json').then((navData) => { this.navOptions = mapNavOptions(navData.default); - this.resolveExternalLinks(configData.default.baseUrl); + this.resolveExternalLinks(APP_CONFIG.baseUrl); }); } diff --git a/projects/website-angular/src/config/api-routes.ts b/projects/website-angular/src/config/api-routes.ts new file mode 100644 index 0000000..bc3b857 --- /dev/null +++ b/projects/website-angular/src/config/api-routes.ts @@ -0,0 +1,14 @@ +import { getEnv } from './environments'; + +const env = getEnv(process.env['APP_ENV'] || (typeof window !== 'undefined' && (window as any).__APP_ENV) || undefined); + +export const API_ROUTES = { + CONTENT_SERVICE: `${env.host}/ContentService`, + ANALYSIS_SERVICE: `${env.host}/AnalysisService`, + EXPERIMENT_SERVICE: `${env.host}/experiment`, + RESTFUL_API: `${env.host}/ReactomeRESTfulAPI/RESTfulWS`, + DOWNLOAD: `${env.host}/download/current`, + OVERLAYS: `${env.host}/overlays`, + CONTENT_DETAIL: `${env.host}/content/detail`, + CONTENT_QUERY: `${env.host}/content/query` +} as const; \ No newline at end of file diff --git a/projects/website-angular/src/config/config.ts b/projects/website-angular/src/config/config.ts new file mode 100644 index 0000000..0a8b651 --- /dev/null +++ b/projects/website-angular/src/config/config.ts @@ -0,0 +1,29 @@ +export const APP_CONFIG = { + // app-level version info + version: { + label: 'V95', + releaseNumber: '95', + releaseDate: '2025-12-09' + }, + + // canonical site base / download base (choose prod by default) + baseUrl: 'https://reactome.org', + downloadUrl: 'https://download.reactome.org', + + // pathway browser config + pathwayBrowser: { + stablePath: '/PathwayBrowser', + betaPath: '/beta/PathwayBrowser', + useBeta: false // default; can be toggled per env or flag + }, + + // release notes (can be computed by the app using version.releaseNumber) + // if you'd like to point at a specific article, set here; otherwise use derived URIs + releaseNotesPath: '/about/news', + + // default flags (feature flags) + features: { + betaPathwayBrowser: false, + useNewAnalysisService: false + } +} as const; \ No newline at end of file diff --git a/projects/website-angular/src/config/environments.ts b/projects/website-angular/src/config/environments.ts new file mode 100644 index 0000000..c5f5dc3 --- /dev/null +++ b/projects/website-angular/src/config/environments.ts @@ -0,0 +1,43 @@ +export type EnvName = 'development' | 'production' | 'local' | 'github' | 'remote'; + +export const ENVIRONMENTS: Record = { + development: { + host: 'https://dev.reactome.org', + s3: 'https://download.reactome.org', + gsaServer: 'dev', + gtagId: 'G-96F1EYHQR3', + preferS3: false + }, + production: { + host: 'https://reactome.org', + s3: 'https://download.reactome.org', + gsaServer: 'production', + gtagId: 'G-EDHZ92GXZP', + preferS3: true + }, + local: { + host: 'http://localhost:4200', + s3: 'https://download.reactome.org', + gsaServer: 'dev', + preferS3: false + }, + github: { + host: '../..', + s3: 'https://download.reactome.org', + gsaServer: 'production', + preferS3: true + }, + remote: { + host: 'https://dev.reactome.org', + s3: 'https://download.reactome.org', + gsaServer: 'dev', + preferS3: false + } +} as const; + +// Helper: pick environment at runtime (fallback to production) +export function getEnv(envName?: string) { + if (!envName) return ENVIRONMENTS.production; + const key = envName as EnvName; + return ENVIRONMENTS[key] ?? ENVIRONMENTS.production; +} \ No newline at end of file diff --git a/projects/website-angular/src/config/external-links.ts b/projects/website-angular/src/config/external-links.ts new file mode 100644 index 0000000..46091e5 --- /dev/null +++ b/projects/website-angular/src/config/external-links.ts @@ -0,0 +1,20 @@ +import { APP_CONFIG } from './config'; + +export const EXTERNAL_LINKS = { + twitter: { label: 'Twitter', link: 'https://twitter.com/Reactome' }, + facebook: { label: 'Facebook', link: 'https://www.facebook.com/reactome' }, + youtube: { label: 'Youtube', link: 'https://www.youtube.com/@Reactome' }, + github: { label: 'GitHub', link: 'https://github.com/reactome' }, + bluesky: { label: 'Bluesky', link: 'https://bsky.app/profile/reactome.org' }, + linkedin: { label: 'LinkedIn', link: 'https://ca.linkedin.com/company/reactome-group' }, + elixir: { label: 'elixir', link: 'https://elixir-europe.org/platforms/data/core-data-resources' }, + gcdr: { label: 'GCDR', link: 'https://globalbiodata.org/scientific-activities/global-core-biodata-resources/' }, + coretrustseal: { label: 'CoreTrustSeal', link: 'https://www.coretrustseal.org/' }, + ebi: { label: 'EBI', link: 'http://www.ebi.ac.uk/' }, + nyu: { label: 'NYU', link: 'https://med.nyu.edu/' }, + ohsu: { label: 'OHSU', link: 'http://www.ohsu.edu/' }, + oicr: { label: 'OICR', link: 'https://oicr.on.ca/' }, + // dynamically construct release notes link using APP_CONFIG + releaseNotes: { label: 'Release Notes', link: `${APP_CONFIG.baseUrl}${APP_CONFIG.releaseNotesPath}` }, + feedback: { label: 'Feedback', link: 'https://forms.gle/TPBxaWnnVLLZj66p8' } +} as const; \ No newline at end of file diff --git a/projects/website-angular/src/config/features.ts b/projects/website-angular/src/config/features.ts new file mode 100644 index 0000000..95122bc --- /dev/null +++ b/projects/website-angular/src/config/features.ts @@ -0,0 +1,5 @@ +export const FEATURES = { + betaPathwayBrowser: false, + useNewAnalysisService: false, + // add other feature flags here and flip them by editing this file or via an external mechanism +} as const; \ No newline at end of file diff --git a/projects/website-angular/src/services/stats.service.ts b/projects/website-angular/src/services/stats.service.ts index 486d7b5..704e402 100644 --- a/projects/website-angular/src/services/stats.service.ts +++ b/projects/website-angular/src/services/stats.service.ts @@ -2,6 +2,7 @@ import { Injectable, inject, PLATFORM_ID } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; import { HttpClient } from '@angular/common/http'; import { Observable, map, catchError, of, timeout } from 'rxjs'; +import { APP_CONFIG } from '../config/config'; // NEW import export interface ReactomeStats { pathways: number; @@ -26,30 +27,23 @@ export class StatsService { private isBrowser = isPlatformBrowser(this.platformId); /** - * Get the current Reactome version + * Get the current Reactome version from APP_CONFIG */ async getVersion(): Promise { - let version = await import('../config/config.json').then( - (data) => data.default.version.releaseNumber - ); - return version; + return APP_CONFIG.version.releaseNumber; } /** - * Get the download base URL + * Get the download base URL from APP_CONFIG */ async getDownloadBaseUrl(): Promise { - let downloadUrl = await import('../config/config.json').then( - (data) => data.default.downloadurl - ); - - return downloadUrl; + return APP_CONFIG.downloadUrl; } /** * Fetch stats from the S3/CloudFront download directory */ - async getStats():Promise> { + async getStats(): Promise> { const defaultStats: ReactomeStats = { pathways: 0, reactions: 0,