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
514 changes: 403 additions & 111 deletions src/frontend/static/frontend/package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions src/frontend/static/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"style-loader": "^4.0.0",
"ts-checker-rspack-plugin": "^1.1.1",
"ts-loader": "^9.5.2",
"typescript": "^6.0.3",
"typescript": "^5.9.3",
"typescript-eslint": "^8.59.3",
"url-loader": "^4.1.1",
"webpack-bundle-tracker": "^3.1.1"
Expand All @@ -72,6 +72,7 @@
"react-avatar": "^5.0.3",
"react-colorful": "^5.6.1",
"react-dom": "^18.3.1",
"react-intl": "^6.8.9",
"react-markdown": "^10.1.0",
"react-plotly.js": "^2.6.0",
"react-virtualized": "^9.22.6",
Expand All @@ -88,4 +89,4 @@
"react-app"
]
}
}
}
3 changes: 2 additions & 1 deletion src/frontend/static/frontend/rspack.common.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export const common = {
survival: `${PATHS.src}/survival.tsx`,
aboutUs: `${PATHS.src}/about-us.tsx`,
openSource: `${PATHS.src}/open-source.tsx`,
sitePolicy: `${PATHS.src}/site-policy.tsx`
sitePolicy: `${PATHS.src}/site-policy.tsx`,
faq: `${PATHS.src}/faq.tsx`
},
output: {
path: PATHS.output,
Expand Down
48 changes: 35 additions & 13 deletions src/frontend/static/frontend/src/components/Base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,26 @@ import ky from 'ky'
import { DjangoUser } from '../utils/django_interfaces'
import { Nullable } from '../utils/interfaces'
import { Footer } from './Footer'
import { ChatWidget } from './assistant/ChatWidget'
import { IntlProvider } from 'react-intl'

// Locales
import es from '../locales/es'
import en from '../locales/en'

// Common dependencies for all the pages
import 'fomantic-ui-css/semantic.css'
import '../css/base.css'

const messages = { en, es }

interface LocaleContextType {
locale: 'en' | 'es',
setLocale: React.Dispatch<React.SetStateAction<'en' | 'es'>>
}
const LocaleContext = React.createContext<LocaleContextType>({
locale: 'es',
setLocale: () => {}
})

declare const urlCurrentUser: string

Expand All @@ -30,6 +49,8 @@ const Base = (props: BaseProps) => {
const abortController = useRef(new AbortController())
const [currentUser, setUser] = useState<Nullable<DjangoUser>>(null)
const [isLoadingCurrentUser, setIsLoadingCurrentUser] = useState<boolean>(true)
// State that defines the current language ('es' or 'en') for <IntlProvider>, used to display the interface in the selected locale
const [locale, setLocale] = useState<'en' | 'es'>('es')

/**
* Method which is executed when the component has mounted
Expand Down Expand Up @@ -76,20 +97,21 @@ const Base = (props: BaseProps) => {

return (
<CurrentUserContext.Provider value={currentUser}>
{/* Navbar */}
<MainNavbar activeItem={props.activeItem} isLoadingUser={isLoadingCurrentUser} />

{/* Composition part */}
<div className={props.wrapperClass}>
{props.children}
</div>
<LocaleContext.Provider value={{ locale, setLocale }}>
<IntlProvider locale={locale} messages={messages[locale]}>
{/* Navbar */}
<MainNavbar activeItem={props.activeItem} isLoadingUser={isLoadingCurrentUser} />

{/* Footer */}
{/* TODO: add license */}
<Footer />
{/* Composition part */}
<div className={props.wrapperClass}>
{props.children}
</div>

{/* AI Assistant floating widget */}
{currentUser && !currentUser.is_anonymous && <ChatWidget />}
{/* Footer */}
{/* TODO: add license */}
<Footer />
</IntlProvider>
</LocaleContext.Provider>
</CurrentUserContext.Provider>
)
}
Expand Down
6 changes: 5 additions & 1 deletion src/frontend/static/frontend/src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react'
import { Container, Grid, Icon, Segment } from 'semantic-ui-react'
import { useIntl } from 'react-intl'

declare const multiomixVersion: string
declare const urlSitePolicy: string
Expand All @@ -9,6 +10,7 @@ declare const urlSitePolicy: string
* @returns Component.
*/
export const Footer = () => {
const intl = useIntl()
return (
<Segment id='footer-component' inverted vertical>
<Container>
Expand Down Expand Up @@ -40,7 +42,9 @@ export const Footer = () => {
href={urlSitePolicy}
rel='noreferrer'
>
Terms and privacy policy
{intl.formatMessage({
id: 'footer.termsAndPrivacyPolicy'
})}
</a>
</p>
</Grid.Column>
Expand Down
55 changes: 35 additions & 20 deletions src/frontend/static/frontend/src/components/MainNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ConfirmModal, CustomAlert, CustomAlertTypes, Nullable } from '../utils/
import { CurrentUserContext } from './Base'
import { UpdateUserModal } from './UpdateUserModal'
import { Alert } from './common/Alert'
import { useIntl } from 'react-intl'

// Constants declared in base.html
declare const urlIndex: string
Expand All @@ -30,6 +31,8 @@ interface LogInLogOutPanelProps {
* @returns Component
*/
const LogInLogOutPanel = (props: LogInLogOutPanelProps) => {
const intl = useIntl()

const getDefaultConfirmModal = (): ConfirmModal => {
return {
confirmModal: false,
Expand Down Expand Up @@ -111,18 +114,18 @@ const LogInLogOutPanel = (props: LogInLogOutPanelProps) => {
if (props.currentUser.is_anonymous) {
return (
<Menu.Item as='a' href={urlLogin} style={{ fontSize: '1rem' }}>
Log in
{intl.formatMessage({ id: 'mainNavbar.login' })}
</Menu.Item>
)
}

// Logged user
return (
<>
<Dropdown text={`Hi, ${props.currentUser.username}`} className='link item' aria-label='Login/Logout'>
<Dropdown text={intl.formatMessage({ id: 'mainNavbar.greeting' }, { username: props.currentUser.username })} className='link item' aria-label='Login/Logout'>
<Dropdown.Menu>
<Dropdown.Item icon='user' text='Edit profile' onClick={() => setModal({ ...modal, isOpen: true })} />
<Dropdown.Item icon='power off' text='Exit' as='a' href={urlLogout} />
<Dropdown.Item icon='user' text={intl.formatMessage({ id: 'mainNavbar.editProfile' })} onClick={() => setModal({ ...modal, isOpen: true })} />
<Dropdown.Item icon='power off' text={intl.formatMessage({ id: 'mainNavbar.exit' })} as='a' href={urlLogout} />
</Dropdown.Menu>
</Dropdown>
<UpdateUserModal isOpen={modal.isOpen} handleClose={() => setModal({ ...modal, isOpen: false })} currentUser={props.currentUser} handleChangeConfirmModalState={handleChangeConfirmModalState} handleUpdateAlert={handleUpdateAlert} />
Expand Down Expand Up @@ -150,7 +153,7 @@ const LogInLogOutPanel = (props: LogInLogOutPanelProps) => {
)
}

type ActiveItemOptions = 'home' | 'pipeline' | 'files' | 'cgds' | 'survival' | 'institutions' | 'about-us' | 'biomarkers' | 'open-source' | 'differential-expression'
type ActiveItemOptions = 'home' | 'pipeline' | 'files' | 'cgds' | 'survival' | 'institutions' | 'about-us' | 'biomarkers' | 'open-source' | 'differential-expression' | 'faq'

interface MainNavbarProps {
activeItem?: ActiveItemOptions,
Expand All @@ -163,12 +166,13 @@ interface MainNavbarProps {
* @returns Component
*/
const MainNavbar = (props: MainNavbarProps) => {
const intl = useIntl()
// Gets current user context
const currentUser = useContext(CurrentUserContext)

const cBioPortalOption = (
<Dropdown.Item
text='cBioPortal'
text={intl.formatMessage({ id: 'mainNavbar.cbioportal' })}
icon='cloud'
as='a' href={urlCGDSPanel}
active={props.activeItem === 'cgds'}
Expand All @@ -178,8 +182,8 @@ const MainNavbar = (props: MainNavbarProps) => {
return (
<Menu className='margin-bottom-0' inverted borderless aria-label='Main navigation'>
{/* Logo */}
<Menu.Item as='a' header href={urlIndex} title='Multiomix homepage'>
<Image size='tiny' src='/static/frontend/img/logo.png' alt='Multiomix homepage' />
<Menu.Item as='a' header href={urlIndex} title={intl.formatMessage({ id: 'mainNavbar.logo.title' })}>
<Image size='tiny' src='/static/frontend/img/logo.png' alt={intl.formatMessage({ id: 'mainNavbar.logo.alt' })} />
</Menu.Item>

{/* Loading spinners while user is fetch */}
Expand All @@ -203,13 +207,13 @@ const MainNavbar = (props: MainNavbarProps) => {
{currentUser && !currentUser.is_anonymous && (
<>
<Menu.Menu>
<Dropdown item text='Analysis' className='link item' icon={null} aria-label='Analysis'>
<Dropdown item text={intl.formatMessage({ id: 'mainNavbar.analysis' })} className='link item' icon={null} aria-label='Analysis'>
<Dropdown.Menu>
{/* GEM panel */}
<Dropdown.Item
text='GEM'
text={intl.formatMessage({ id: 'mainNavbar.gem' })}
icon='lab'
title='Gene Expression Modulation'
title={intl.formatMessage({ id: 'mainNavbar.gem.title' })}
as='a' href={urlPipeline}
active={props.activeItem === 'pipeline'}
/>
Expand All @@ -226,15 +230,15 @@ const MainNavbar = (props: MainNavbarProps) => {

{/* Biomarkers panel */}
<Dropdown.Item
text='Biomarkers'
text={intl.formatMessage({ id: 'mainNavbar.biomarkers' })}
icon='list layout'
as='a' href={urlBiomarkers}
active={props.activeItem === 'biomarkers'}
/>

{/* Differential Expression panel */}
<Dropdown.Item
text='Differential expression'
text={intl.formatMessage({ id: 'mainNavbar.differentialExpression' })}
icon='buromobelexperte'
as='a' href={urlDifferentialExpression}
active={props.activeItem === 'differential-expression'}
Expand All @@ -245,11 +249,11 @@ const MainNavbar = (props: MainNavbarProps) => {

{/* Datasets menu */}
<Menu.Menu>
<Dropdown text='Datasets' className='link item' icon={null} aria-label='Datasets'>
<Dropdown text={intl.formatMessage({ id: 'mainNavbar.datasets' })} className='link item' icon={null} aria-label='Datasets'>
<Dropdown.Menu>
{/* User's Datasets panel */}
<Dropdown.Item
text='My datasets'
text={intl.formatMessage({ id: 'mainNavbar.myDatasets' })}
icon='database'
as='a' href={urlDatasets}
active={props.activeItem === 'files'}
Expand All @@ -264,7 +268,7 @@ const MainNavbar = (props: MainNavbarProps) => {
{/* Institutions */}
<Menu.Menu>
<Menu.Item as='a' href={urlInstitutions} style={{ fontSize: '1rem' }}>
Institutions
{intl.formatMessage({ id: 'mainNavbar.institutions' })}
</Menu.Item>
</Menu.Menu>
</>
Expand All @@ -273,13 +277,13 @@ const MainNavbar = (props: MainNavbarProps) => {
{/* Only admin options */}
{currentUser && (currentUser.is_superuser || currentUser.is_institution_admin) && (
<Menu.Menu>
<Dropdown text='Admin' className='link item' icon={null} aria-label='Admin'>
<Dropdown text={intl.formatMessage({ id: 'mainNavbar.admin' })} className='link item' icon={null} aria-label='Admin'>
<Dropdown.Menu>
{currentUser.is_superuser && (
<>
{/* User's Datasets panel */}
<Dropdown.Item
text='Database: Genes'
text={intl.formatMessage({ id: 'mainNavbar.databaseGenes' })}
icon='dna'
// as='a' href={null}
disabled
Expand All @@ -294,13 +298,24 @@ const MainNavbar = (props: MainNavbarProps) => {
{/* About us */}
<Menu.Menu>
<Menu.Item as='a' href={urlAboutUs} style={{ fontSize: '1rem' }}>
About us
{intl.formatMessage({ id: 'mainNavbar.aboutUs' })}
</Menu.Item>
</Menu.Menu>

<Menu.Menu>
<Menu.Item
as='a'
href='/faq'
style={{ fontSize: '1rem' }}
active={props.activeItem === 'faq'}
>
{intl.formatMessage({ id: 'mainNavbar.faq' })}
</Menu.Item>
</Menu.Menu>

<Menu.Menu>
<Menu.Item as='a' href={urlOpenSource} style={{ fontSize: '1rem' }}>
Open source
{intl.formatMessage({ id: 'mainNavbar.openSource' })}
</Menu.Item>
</Menu.Menu>

Expand Down
Loading
Loading