From 782bd1d7d7ef61359ba3e80bd6001dec3098264b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juho=20Veps=C3=A4l=C3=A4inen?= Date: Mon, 9 Mar 2026 13:50:49 +0200 Subject: [PATCH 1/5] feat: Add a resolve demo --- client/src/components/perspectives/Footer.js | 104 +++++++++---------- client/webpack.client.common.js | 79 ++++++++------ 2 files changed, 97 insertions(+), 86 deletions(-) diff --git a/client/src/components/perspectives/Footer.js b/client/src/components/perspectives/Footer.js index 4c159946..031f58f2 100644 --- a/client/src/components/perspectives/Footer.js +++ b/client/src/components/perspectives/Footer.js @@ -1,114 +1,114 @@ -import React from 'react' -import Paper from '@mui/material/Paper' -import PropTypes from 'prop-types' -import Box from '@mui/material/Box' -import aaltoLogo from '../../img/logos/Aalto_SCI_EN_13_BLACK_2_cropped.png' -import hyLogo from '../../img/logos/university-of-helsinki-logo-transparent-black.png' -import heldigLogo from '../../img/logos/heldig-logo-transparent-black.png' +import React from "react"; +import Paper from "@mui/material/Paper"; +import PropTypes from "prop-types"; +import Box from "@mui/material/Box"; +import aaltoLogo from "~/img/logos/Aalto_SCI_EN_13_BLACK_2_cropped.png"; +import hyLogo from "~/img/logos/university-of-helsinki-logo-transparent-black.png"; +import heldigLogo from "~/img/logos/heldig-logo-transparent-black.png"; /** * A component for creating a footer. The logos are imported inside this component. */ -const Footer = props => { +const Footer = (props) => { return ( ({ - boxShadow: '0 -20px 20px -20px #333', + sx={(theme) => ({ + boxShadow: "0 -20px 20px -20px #333", borderRadius: 0, - display: 'flex', - justifyContent: 'space-evenly', - alignItems: 'center', - flexWrap: 'wrap', + display: "flex", + justifyContent: "space-evenly", + alignItems: "center", + flexWrap: "wrap", rowGap: theme.spacing(2), columnGap: theme.spacing(3), paddingLeft: theme.spacing(2), paddingRight: theme.spacing(2), [theme.breakpoints.down(496)]: { paddingTop: theme.spacing(2), - paddingBottom: theme.spacing(2) + paddingBottom: theme.spacing(2), }, minHeight: { xs: props.layoutConfig.footer.reducedHeight, hundredPercentHeight: props.layoutConfig.footer.reducedHeight, - reducedHeight: props.layoutConfig.footer.defaultHeight - } + reducedHeight: props.layoutConfig.footer.defaultHeight, + }, })} > ({ + component="a" + href="https://www.aalto.fi/en/school-of-science" + target="_blank" + rel="noopener noreferrer" + sx={(theme) => ({ width: 143, height: 29, [theme.breakpoints.up(props.layoutConfig.reducedHeightBreakpoint)]: { width: 167, - height: 34 - } + height: 34, + }, })} > ({ + component="a" + href="https://www.helsinki.fi/en" + target="_blank" + rel="noopener noreferrer" + sx={(theme) => ({ width: 155, height: 40, [theme.breakpoints.up(props.layoutConfig.reducedHeightBreakpoint)]: { width: 168, - height: 45 - } + height: 45, + }, })} > ({ + component="a" + href="https://www.helsinki.fi/en/helsinki-centre-for-digital-humanities" + target="_blank" + rel="noopener noreferrer" + sx={(theme) => ({ width: 118, height: 30, [theme.breakpoints.up(props.layoutConfig.reducedHeightBreakpoint)]: { width: 130, - height: 33 - } + height: 33, + }, })} > - ) -} + ); +}; Footer.propTypes = { - layoutConfig: PropTypes.object.isRequired -} + layoutConfig: PropTypes.object.isRequired, +}; -export default Footer +export default Footer; diff --git a/client/webpack.client.common.js b/client/webpack.client.common.js index b9035f7b..fc3e4a4d 100644 --- a/client/webpack.client.common.js +++ b/client/webpack.client.common.js @@ -1,16 +1,22 @@ -const path = require('path') -require('dotenv').config() -const HtmlWebpackPlugin = require('html-webpack-plugin') -const { CleanWebpackPlugin } = require('clean-webpack-plugin') -const webpack = require('webpack') +const path = require("path"); +require("dotenv").config(); +const HtmlWebpackPlugin = require("html-webpack-plugin"); +const { CleanWebpackPlugin } = require("clean-webpack-plugin"); +const webpack = require("webpack"); -const outputDirectory = 'dist/public' -const apiUrl = typeof process.env.API_URL !== 'undefined' ? process.env.API_URL : 'http://localhost:3001/api/v1' -const mapboxAccessToken = typeof process.env.MAPBOX_ACCESS_TOKEN !== 'undefined' ? process.env.MAPBOX_ACCESS_TOKEN : 'MAPBOX_ACCESS_TOKEN missing' +const outputDirectory = "dist/public"; +const apiUrl = + typeof process.env.API_URL !== "undefined" + ? process.env.API_URL + : "http://localhost:3001/api/v1"; +const mapboxAccessToken = + typeof process.env.MAPBOX_ACCESS_TOKEN !== "undefined" + ? process.env.MAPBOX_ACCESS_TOKEN + : "MAPBOX_ACCESS_TOKEN missing"; module.exports = { entry: { - app: './src/index.js' + app: "./src/index.js", }, plugins: [ /** @@ -21,61 +27,66 @@ module.exports = { * * During rebuilds, all webpack assets that are not used anymore * will be removed automatically. - */ + */ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ // Load a custom template - template: 'src/index.html', - favicon: 'src/favicon.ico' + template: "src/index.html", + favicon: "src/favicon.ico", }), new webpack.DefinePlugin({ - 'process.env.API_URL': JSON.stringify(apiUrl), - 'process.env.MAPBOX_ACCESS_TOKEN': JSON.stringify(mapboxAccessToken) - }) + "process.env.API_URL": JSON.stringify(apiUrl), + "process.env.MAPBOX_ACCESS_TOKEN": JSON.stringify(mapboxAccessToken), + }), ], output: { - filename: '[name].[fullhash].js', + filename: "[name].[fullhash].js", path: path.resolve(__dirname, outputDirectory), - publicPath: '/' + publicPath: "/", }, module: { rules: [ { test: /\.m?js$/, exclude: /node_modules/, - use: ['babel-loader'] + use: ["babel-loader"], }, { test: /\.s?css$/, use: [ // Creates `style` nodes from JS strings - 'style-loader', + "style-loader", // Translates CSS into CommonJS - 'css-loader', + "css-loader", // Compiles Sass to CSS - 'sass-loader' - ] + "sass-loader", + ], }, { test: /\.(png|svg|jpg|gif)$/, - loader: 'file-loader', + loader: "file-loader", options: { - outputPath: 'images' - } + outputPath: "images", + }, }, { test: /\.(woff2|woff|eot|ttf)$/, - loader: 'file-loader', + loader: "file-loader", options: { - name: 'fonts/[name].[ext]' - } - } - ] + name: "fonts/[name].[ext]", + }, + }, + ], }, resolve: { - extensions: ['.js', '.jsx'] + extensions: [".js", ".jsx"], + alias: { + // maps @something to path/to/something + // See https://webpack.js.org/configuration/resolve/ for more information + "~*": path.resolve(__dirname, "src/*"), + }, }, experiments: { - topLevelAwait: true - } -} + topLevelAwait: true, + }, +}; From 470cca75d5b406f99992654765bd5970d9dde335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juho=20Veps=C3=A4l=C3=A4inen?= Date: Mon, 9 Mar 2026 14:20:51 +0200 Subject: [PATCH 2/5] feat: Add a replacement prototype --- .../custom/components/perspectives/Footer.js | 68 ++++ client/src/containers/SemanticPortal.js | 325 ++++++++++-------- client/webpack.client.common.js | 5 + 3 files changed, 252 insertions(+), 146 deletions(-) create mode 100644 client/custom/components/perspectives/Footer.js diff --git a/client/custom/components/perspectives/Footer.js b/client/custom/components/perspectives/Footer.js new file mode 100644 index 00000000..9a16faed --- /dev/null +++ b/client/custom/components/perspectives/Footer.js @@ -0,0 +1,68 @@ +import React from "react"; +import Paper from "@mui/material/Paper"; +import PropTypes from "prop-types"; +import Box from "@mui/material/Box"; +import aaltoLogo from "~/img/logos/Aalto_SCI_EN_13_BLACK_2_cropped.png"; +import hyLogo from "~/img/logos/university-of-helsinki-logo-transparent-black.png"; +import heldigLogo from "~/img/logos/heldig-logo-transparent-black.png"; + +/** + * A component for creating a footer. The logos are imported inside this component. + */ +const Footer = (props) => { + return ( + ({ + boxShadow: "0 -20px 20px -20px #333", + borderRadius: 0, + display: "flex", + justifyContent: "space-evenly", + alignItems: "center", + flexWrap: "wrap", + rowGap: theme.spacing(2), + columnGap: theme.spacing(3), + paddingLeft: theme.spacing(2), + paddingRight: theme.spacing(2), + [theme.breakpoints.down(496)]: { + paddingTop: theme.spacing(2), + paddingBottom: theme.spacing(2), + }, + minHeight: { + xs: props.layoutConfig.footer.reducedHeight, + hundredPercentHeight: props.layoutConfig.footer.reducedHeight, + reducedHeight: props.layoutConfig.footer.defaultHeight, + }, + })} + > + ({ + width: 143, + height: 29, + [theme.breakpoints.up(props.layoutConfig.reducedHeightBreakpoint)]: { + width: 167, + height: 34, + }, + })} + > + + + + ); +}; + +Footer.propTypes = { + layoutConfig: PropTypes.object.isRequired, +}; + +export default Footer; diff --git a/client/src/containers/SemanticPortal.js b/client/src/containers/SemanticPortal.js index ac6521d8..0f91d423 100644 --- a/client/src/containers/SemanticPortal.js +++ b/client/src/containers/SemanticPortal.js @@ -1,10 +1,10 @@ -import React, { useEffect, lazy } from 'react' -import PropTypes from 'prop-types' -import intl from 'react-intl-universal' -import { has } from 'lodash' -import { connect } from 'react-redux' -import { Route, Redirect, Switch, useLocation } from 'react-router-dom' -import Box from '@mui/material/Box' +import React, { useEffect, lazy } from "react"; +import PropTypes from "prop-types"; +import intl from "react-intl-universal"; +import { has } from "lodash"; +import { connect } from "react-redux"; +import { Route, Redirect, Switch, useLocation } from "react-router-dom"; +import Box from "@mui/material/Box"; import { fetchResultCount, fetchPaginatedResults, @@ -37,33 +37,40 @@ import { clientFSClearResults, clientFSUpdateQuery, clientFSUpdateFacet, - fetchKnowledgeGraphMetadata -} from '../actions' -import { filterResults } from '../selectors' -import { - getScreenSize, - usePageViews -} from '../helpers/helpers' -import * as apexChartsConfig from '../library_configs/ApexCharts/ApexChartsConfig' -import * as leafletConfig from '../library_configs/Leaflet/LeafletConfig' -import * as networkToolsGeneral from '../library_configs/Cytoscape.js/NetworkToolsGeneral' -import * as networkToolsPortalSpecific from '../library_configs/Cytoscape.js/NetworkToolsPortalSpecific' -import { useConfigsStore } from '../stores/configsStore' + fetchKnowledgeGraphMetadata, +} from "../actions"; +import { filterResults } from "../selectors"; +import { getScreenSize, usePageViews } from "../helpers/helpers"; +import * as apexChartsConfig from "../library_configs/ApexCharts/ApexChartsConfig"; +import * as leafletConfig from "../library_configs/Leaflet/LeafletConfig"; +import * as networkToolsGeneral from "../library_configs/Cytoscape.js/NetworkToolsGeneral"; +import * as networkToolsPortalSpecific from "../library_configs/Cytoscape.js/NetworkToolsPortalSpecific"; +import { useConfigsStore } from "../stores/configsStore"; // ** Import general components ** -const TopBar = lazy(() => import('../components/main_layout/TopBar')) -const TextPage = lazy(() => import('../components/main_layout/TextPage')) -const Message = lazy(() => import('../components/main_layout/Message')) -const FullTextSearch = lazy(() => import('../components/main_layout/FullTextSearch')) -const FacetedSearchPerspective = lazy(() => import('../components/facet_results/FacetedSearchPerspective')) -const FederatedSearchPerspective = lazy(() => import('../components/facet_results/FederatedSearchPerspective')) -const InstancePagePerspective = lazy(() => import('../components/main_layout/InstancePagePerspective')) -const KnowledgeGraphMetadataTable = lazy(() => import('../components/main_layout/KnowledgeGraphMetadataTable')) +const TopBar = lazy(() => import("../components/main_layout/TopBar")); +const TextPage = lazy(() => import("../components/main_layout/TextPage")); +const Message = lazy(() => import("../components/main_layout/Message")); +const FullTextSearch = lazy( + () => import("../components/main_layout/FullTextSearch"), +); +const FacetedSearchPerspective = lazy( + () => import("../components/facet_results/FacetedSearchPerspective"), +); +const FederatedSearchPerspective = lazy( + () => import("../components/facet_results/FederatedSearchPerspective"), +); +const InstancePagePerspective = lazy( + () => import("../components/main_layout/InstancePagePerspective"), +); +const KnowledgeGraphMetadataTable = lazy( + () => import("../components/main_layout/KnowledgeGraphMetadataTable"), +); // ** General components end ** // ** Import portal specific components ** -const Main = lazy(() => import('../components/perspectives/Main')) -const Footer = lazy(() => import('../components/perspectives/Footer')) +const Main = lazy(() => import("components/perspectives/Main")); +const Footer = lazy(() => import("components/perspectives/Footer")); // ** Portal specific components end ** /** @@ -71,57 +78,53 @@ const Footer = lazy(() => import('../components/perspectives/Footer')) * the main routes of the portal are defined here based on JSON configs, using React Router. * Currently, it is not possible to render this component in Storybook. */ -const SemanticPortal = props => { - const { - portalConfig, - perspectiveConfigs, - perspectiveConfigsInfoOnlyPages - } = useConfigsStore() +const SemanticPortal = (props) => { + const { portalConfig, perspectiveConfigs, perspectiveConfigsInfoOnlyPages } = + useConfigsStore(); - const { - rootUrl, - layoutConfig, - knowledgeGraphMetadataConfig - } = portalConfig + const { rootUrl, layoutConfig, knowledgeGraphMetadataConfig } = portalConfig; const networkConfig = { ...networkToolsGeneral, - ...networkToolsPortalSpecific - } + ...networkToolsPortalSpecific, + }; - const { error } = props - const location = useLocation() - const rootUrlWithLang = `${rootUrl}/${props.options.currentLocale}` - const screenSize = getScreenSize() - const federatedSearchPerspectives = [] - let noClientFSResults = true - perspectiveConfigs.forEach(perspective => { - if (perspective.searchMode === 'federated-search') { - federatedSearchPerspectives.push(perspective) - noClientFSResults = props.clientFSState && props.clientFSState.results === null + const { error } = props; + const location = useLocation(); + const rootUrlWithLang = `${rootUrl}/${props.options.currentLocale}`; + const screenSize = getScreenSize(); + const federatedSearchPerspectives = []; + let noClientFSResults = true; + perspectiveConfigs.forEach((perspective) => { + if (perspective.searchMode === "federated-search") { + federatedSearchPerspectives.push(perspective); + noClientFSResults = + props.clientFSState && props.clientFSState.results === null; } - }) + }); // trigger a new "page view" event whenever a new page loads - usePageViews() + usePageViews(); // set HTML title and description dynamically based on translations useEffect(() => { - document.title = intl.get('html.title') - document.documentElement.lang = props.options.currentLocale - document.querySelector('meta[name="description"]').setAttribute('content', intl.get('html.description')) - }, [props.options.currentLocale]) + document.title = intl.get("html.title"); + document.documentElement.lang = props.options.currentLocale; + document + .querySelector('meta[name="description"]') + .setAttribute("content", intl.get("html.description")); + }, [props.options.currentLocale]); return ( ({ - backgroundColor: '#bdbdbd', - overflowX: 'hidden', - minHeight: '100%', + sx={(theme) => ({ + backgroundColor: "#bdbdbd", + overflowX: "hidden", + minHeight: "100%", [theme.breakpoints.up(layoutConfig.hundredPercentHeightBreakPoint)]: { - overflow: 'hidden', - height: '100%' - } + overflow: "hidden", + height: "100%", + }, })} > {/* error messages are shown on top of the app */} @@ -155,17 +158,14 @@ const SemanticPortal = props => { rootUrl={rootUrlWithLang} layoutConfig={layoutConfig} /> -