From 2ef20a3d0c04e1579b79377c6e778f2029f272de Mon Sep 17 00:00:00 2001 From: Gang Li Date: Sat, 2 Dec 2023 14:38:13 +0800 Subject: [PATCH] Add loading spiner and use it for remote content fetching --- src/main/webui/.eslintrc.json | 2 +- .../content/common/LoadingSpiner.jsx | 22 +++++++++++ .../components/content/remote/RemoteList.jsx | 8 ++++ .../components/content/remote/RemoteView.jsx | 8 ++++ .../webui/src/app/components/styles/indy.css | 9 +++++ src/main/webui/src/server/app.js | 37 +++++++++++++------ 6 files changed, 74 insertions(+), 12 deletions(-) create mode 100644 src/main/webui/src/app/components/content/common/LoadingSpiner.jsx diff --git a/src/main/webui/.eslintrc.json b/src/main/webui/.eslintrc.json index f91204b..053a2f9 100644 --- a/src/main/webui/.eslintrc.json +++ b/src/main/webui/.eslintrc.json @@ -230,7 +230,7 @@ "nonblock-statement-body-position": 2, "object-curly-newline": 2, "object-curly-spacing": [ - 2, + 1, "never" ], "object-property-newline": 0, diff --git a/src/main/webui/src/app/components/content/common/LoadingSpiner.jsx b/src/main/webui/src/app/components/content/common/LoadingSpiner.jsx new file mode 100644 index 0000000..d9c90b0 --- /dev/null +++ b/src/main/webui/src/app/components/content/common/LoadingSpiner.jsx @@ -0,0 +1,22 @@ +/** + * Copyright (C) 2023 Red Hat, Inc. (https://github.com/Commonjava/indy-ui-service) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; + +export const LoadingSpiner = () =>
+ Loading... +
; + diff --git a/src/main/webui/src/app/components/content/remote/RemoteList.jsx b/src/main/webui/src/app/components/content/remote/RemoteList.jsx index df6f139..0ab6f6e 100644 --- a/src/main/webui/src/app/components/content/remote/RemoteList.jsx +++ b/src/main/webui/src/app/components/content/remote/RemoteList.jsx @@ -20,6 +20,7 @@ import {ListJsonDebugger} from '../common/Debugger.jsx'; import ListControl from "../common/ListControl.jsx"; import {remoteOptionLegend as options, STORE_API_BASE_URL} from "../../ComponentConstants.js"; import {StoreListingWidget} from '../common/StoreListingWidget.jsx'; +import {LoadingSpiner} from "../common/LoadingSpiner.jsx"; import {Utils} from '#utils/AppUtils.js'; import {jsonRest} from '#utils/RestClient.js'; @@ -52,8 +53,10 @@ export default function RemoteList() { enableDebug: false, message: '' }); + const [loading, setLoading] = useState(true); useEffect(()=>{ + setLoading(true); const fetchdData = async ()=>{ const response = await jsonRest.get(`${STORE_API_BASE_URL}/${packageType}/remote`); if (response.ok){ @@ -81,10 +84,15 @@ export default function RemoteList() { }); }); } + setLoading(false); }; fetchdData(); }, [packageType]); + if (loading) { + return ; + } + return ( { + setLoading(true); const fetchStore = async () => { const response = await jsonRest.get(`${STORE_API_BASE_URL}/${packageType}/remote/${name}`); if (response.ok){ @@ -186,11 +189,16 @@ export default function RemoteView() { Utils.logMessage(`Failed to get store data. Error reason: ${response.status}->${data}`); }); } + setLoading(false); }; fetchStore(); }, [packageType, name]); + if (loading) { + return ; + } + const store = state.store; if(!Utils.isEmptyObj(store)) { return ( diff --git a/src/main/webui/src/app/components/styles/indy.css b/src/main/webui/src/app/components/styles/indy.css index 200b45e..8b8d102 100644 --- a/src/main/webui/src/app/components/styles/indy.css +++ b/src/main/webui/src/app/components/styles/indy.css @@ -445,3 +445,12 @@ label, .label{ float: right; margin-bottom: 6px; } + +.loader { + color: rgb(0, 0, 0); + display: inline-block; + position: relative; + font-size: 24px; + font-family: Arial, Helvetica, sans-serif; + box-sizing: border-box; +} diff --git a/src/main/webui/src/server/app.js b/src/main/webui/src/server/app.js index 8a426b0..b96d59e 100644 --- a/src/main/webui/src/server/app.js +++ b/src/main/webui/src/server/app.js @@ -3,6 +3,7 @@ import compression from 'compression'; import express from 'express'; import path from 'path'; import {Config} from './config/AppConfig'; +import {existsSync} from 'fs'; const projectRoot = path.resolve(__dirname, '../../dist'); const indexHtml=path.join(projectRoot+'/index.html'); @@ -38,33 +39,47 @@ app.get('/api/stats/version-info', (req, res) => { }); const decideMockListFile = (packgeType, type) => { - const pkgToFileMapping = {"maven": "Maven", "npm": "NPM"}; + const pkgToFileMapping = {"maven": "Maven", "generic-http": "Generic", "npm": "NPM"}; const typeToFileMapping = {"remote": "Remote", "hosted": "Hosted", "group": "Group"}; - return `./mock/list/Fake${pkgToFileMapping[packgeType]}${typeToFileMapping[type]}List.json`; + path.resolve(__dirname, './file-location'); + return path.resolve(__dirname, `./mock/list/Fake${pkgToFileMapping[packgeType]}${typeToFileMapping[type]}List.json`); }; + +const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); + // For store listing -app.get(`${STORE_API_BASE}/:packageType/:type`, (req, res) => { +app.get(`${STORE_API_BASE}/:packageType/:type`, async (req, res) => { + await sleep(2000); const [pkgType, type] = [req.params.packageType, req.params.type]; if(pkgType==="_all"){ // TODO: do all packageType for type handling here } const mockFile = decideMockListFile(pkgType, type); - const list = require(mockFile); - res.status(200).json(list); + if(existsSync(mockFile)){ + const list = require(mockFile); + res.status(200).json(list); + }else{ + res.status(404).json({error: "No such stores!"}); + } }); // For single store get -app.get(`${STORE_API_BASE}/:packageType/:type/:name`, (req, res) => { +app.get(`${STORE_API_BASE}/:packageType/:type/:name`, async (req, res) => { + await sleep(1000); const [pkgType, type, name] = [req.params.packageType, req.params.type, req.params.name]; if(pkgType && type && name){ const mockListFile = decideMockListFile(pkgType, type); - const repoList = require(mockListFile); - const result = repoList.items.find(item=>item.name===name); - if(result){ - res.status(200).json(result); + if(existsSync(mockListFile)){ + const repoList = require(mockListFile); + const result = repoList.items.find(item=>item.name===name); + if(result){ + res.status(200).json(result); + }else{ + res.status(404).json({error: "No such store!"}); + } }else{ - res.status(404).json({error: "No such store!"}); + res.status(404).json({error: "No such stores!"}); } }else{ res.status(400).json({error: "Missing store name"});