Web: Files: Speeded up loading first page + refactoring

This commit is contained in:
Alexey Safronov 2020-09-17 12:07:36 +03:00
parent 2f4a3141d5
commit 9623029d82
4 changed files with 267 additions and 208 deletions

View File

@ -29,12 +29,21 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<style type="text/css">
body{height:100%;margin:0;padding:0;width:100%}body{font-family:'Open Sans',sans-serif;font-size:13px;-webkit-font-smoothing:antialiased}.ipl-progress-indicator.available{opacity:0}.ipl-progress-indicator{background-color:#f5f5f5;width:100%;height:100%;position:fixed;opacity:1;pointer-events:none;-webkit-transition:opacity cubic-bezier(.4,0,.2,1) 436ms;-moz-transition:opacity cubic-bezier(.4,0,.2,1) 436ms;transition:opacity cubic-bezier(.4,0,.2,1) 436ms;z-index:9999}.insp-logo-frame{display:-webkit-flex;display:-moz-flex;display:flex;-webkit-flex-direction:column;-moz-flex-direction:column;flex-direction:column;-webkit-justify-content:center;-moz-justify-content:center;justify-content:center;-webkit-animation:fadein 436ms;-moz-animation:fadein 436ms;animation:fadein 436ms;height:98%}.insp-logo-frame-img{width:112px;height:112px;-webkit-align-self:center;-moz-align-self:center;align-self:center;border-radius:50%}.ipl-progress-indicator-head{background-color:#c6dafc;height:4px;overflow:hidden;position:relative}.ipl-progress-indicator .first-indicator,.ipl-progress-indicator .second-indicator{background-color:#056d8b;bottom:0;left:0;right:0;top:0;position:absolute;-webkit-transform-origin:left center;-moz-transform-origin:left center;transform-origin:left center;-webkit-transform:scaleX(0);-moz-transform:scaleX(0);transform:scaleX(0)}.ipl-progress-indicator .first-indicator{-webkit-animation:first-indicator 2s linear infinite;-moz-animation:first-indicator 2s linear infinite;animation:first-indicator 2s linear infinite}.ipl-progress-indicator .second-indicator{-webkit-animation:second-indicator 2s linear infinite;-moz-animation:second-indicator 2s linear infinite;animation:second-indicator 2s linear infinite}.ipl-progress-indicator .insp-logo{animation:App-logo-spin infinite 20s linear;border-radius:50%;-webkit-align-self:center;-moz-align-self:center;align-self:center}@keyframes App-logo-spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}@-webkit-keyframes fadein{from{opacity:0}to{opacity:1}}@-moz-keyframes fadein{from{opacity:0}to{opacity:1}}@keyframes fadein{from{opacity:0}to{opacity:1}}@keyframes first-indicator{0%{transform:translate(0) scaleX(0)}25%{transform:translate(0) scaleX(.5)}50%{transform:translate(25%) scaleX(.75)}75%{transform:translate(100%) scaleX(0)}100%{transform:translate(100%) scaleX(0)}}@keyframes second-indicator{0%{transform:translate(0) scaleX(0)}60%{transform:translate(0) scaleX(0)}80%{transform:translate(0) scaleX(.6)}100%{transform:translate(100%) scaleX(.1)}}
</style>
<title>ONLYOFFICE</title>
</head>
<body>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
You need to enable JavaScript to run this app.
</noscript>
<div class="ipl-progress-indicator" id="ipl-progress-indicator">
<div class="ipl-progress-indicator-head">
<div class="first-indicator"></div>
<div class="second-indicator"></div>
</div>
</div>
<div id="root"></div>
<!--
This HTML file is a template.

View File

@ -1,12 +1,21 @@
import React, { Suspense } from "react";
import { connect } from "react-redux";
import axios from "axios";
import { Router, Switch, Redirect } from "react-router-dom";
import { Loader } from "asc-web-components";
import Home from "./components/pages/Home";
import DocEditor from "./components/pages/DocEditor";
import Settings from "./components/pages/Settings";
import {
fetchMyFolder,
fetchTreeFolders,
fetchFiles
} from "./store/files/actions";
import config from "../package.json";
import {
store as commonStore,
constants,
history,
PrivateRoute,
PublicRoute,
@ -14,9 +23,25 @@ import {
Error404,
Error520,
StudioLayout,
Offline
Offline,
api
} from "asc-web-common";
import { getFilterByLocation } from "./helpers/converters";
const {
setIsLoaded,
getUser,
getPortalSettings,
getModules,
setCurrentProductId,
setCurrentProductHomePage,
getPortalPasswordSettings,
getPortalCultures
} = commonStore.auth.actions;
const { AUTH_KEY } = constants;
const { FilesFilter } = api;
const VersionHistory = React.lazy(() =>
import("./components/pages/VersionHistory")
);
@ -27,63 +52,218 @@ const withStudioLayout = Component => props => (
</StudioLayout>
);
const App = ({ settings }) => {
const { homepage } = settings;
class App extends React.Component {
removeLoader = () => {
const ele = document.getElementById("ipl-progress-indicator");
if (ele) {
// fade out
ele.classList.add("available");
setTimeout(() => {
// remove from DOM
ele.outerHTML = "";
}, 2000);
}
};
return navigator.onLine ? (
<Router history={history}>
<Suspense
fallback={<Loader className="pageLoader" type="rombs" size="40px" />}
>
<Switch>
<Redirect exact from="/" to={`${homepage}`} />
<PrivateRoute
exact
path={[homepage, `${homepage}/filter`]}
component={withStudioLayout(Home)}
/>
<PrivateRoute
exact
path={`${homepage}/settings/:setting`}
component={withStudioLayout(Settings)}
/>
<PrivateRoute
exact
path={`${homepage}/doceditor`}
component={DocEditor}
/>
<PrivateRoute
exact
path={`${homepage}/:fileId/history`}
component={withStudioLayout(VersionHistory)}
/>
<PublicRoute
exact
path={[
"/login",
"/login/error=:error",
"/login/confirmed-email=:confirmedEmail"
]}
component={withStudioLayout(Login)}
/>
<PrivateRoute
exact
path={`/error=:error`}
component={withStudioLayout(Error520)}
/>
<PrivateRoute component={withStudioLayout(Error404)} />
</Switch>
</Suspense>
</Router>
) : (
<Offline />
);
};
componentDidMount() {
const {
getUser,
getPortalSettings,
getModules,
getPortalPasswordSettings,
getPortalCultures,
fetchMyFolder,
fetchTreeFolders,
fetchFiles,
finalize,
setIsLoaded
} = this.props;
function mapStateToProps(state) {
const token = localStorage.getItem(AUTH_KEY);
if (!token) {
this.removeLoader();
return setIsLoaded();
}
const requests = [
getUser(),
getPortalSettings(),
getModules(),
getPortalPasswordSettings(),
getPortalCultures(),
fetchMyFolder(),
fetchTreeFolders()
];
axios
.all(requests)
.then(() => {
const reg = new RegExp(`${config.homepage}((/?)$|/filter)`, "gm"); //TODO: Always find?
const match = window.location.pathname.match(reg);
let filterObj = null;
if (match && match.length > 0) {
filterObj = getFilterByLocation(window.location);
if (!filterObj) {
filterObj = FilesFilter.getDefault();
}
}
return Promise.resolve(filterObj);
})
.then(filter => {
let dataObj = filter;
if (filter && filter.authorType) {
const filterObj = filter;
const authorType = filterObj.authorType;
const indexOfUnderscore = authorType.indexOf("_");
const type = authorType.slice(0, indexOfUnderscore);
const itemId = authorType.slice(indexOfUnderscore + 1);
if (itemId) {
dataObj = {
type,
itemId,
filter: filterObj
};
} else {
filterObj.authorType = null;
dataObj = filterObj;
}
}
return Promise.resolve(dataObj);
})
.then(data => {
if (!data) return Promise.resolve();
if (data instanceof FilesFilter) return Promise.resolve(data);
const { filter, itemId, type } = data;
const newFilter = filter ? filter.clone() : FilesFilter.getDefault();
switch (type) {
case "group":
return Promise.all([api.groups.getGroup(itemId), newFilter]);
case "user":
return Promise.all([api.people.getUserById(itemId), newFilter]);
default:
return Promise.resolve(newFilter);
}
})
.catch(err => {
Promise.resolve(FilesFilter.getDefault());
console.warn("Filter restored by default", err);
})
.then(data => {
if (!data) return Promise.resolve();
if (data instanceof FilesFilter) return Promise.resolve(data);
const result = data[0];
const filter = data[1];
const type = result.displayName ? "user" : "group";
const selectedItem = {
key: result.id,
label: type === "user" ? result.displayName : result.name,
type
};
filter.selectedItem = selectedItem;
return Promise.resolve(filter);
})
.then(filter => {
if (!filter) return Promise.resolve();
const folderId = filter.folder;
return fetchFiles(folderId, filter);
})
.then(() => {
this.removeLoader();
finalize();
});
}
render() {
const { homepage } = this.props.settings;
return navigator.onLine ? (
<Router history={history}>
<Suspense
fallback={<Loader className="pageLoader" type="rombs" size="40px" />}
>
<Switch>
<Redirect exact from="/" to={`${homepage}`} />
<PrivateRoute
exact
path={[homepage, `${homepage}/filter`]}
component={withStudioLayout(Home)}
/>
<PrivateRoute
exact
path={`${homepage}/settings/:setting`}
component={withStudioLayout(Settings)}
/>
<PrivateRoute
exact
path={`${homepage}/doceditor`}
component={DocEditor}
/>
<PrivateRoute
exact
path={`${homepage}/:fileId/history`}
component={withStudioLayout(VersionHistory)}
/>
<PublicRoute
exact
path={[
"/login",
"/login/error=:error",
"/login/confirmed-email=:confirmedEmail"
]}
component={withStudioLayout(Login)}
/>
<PrivateRoute
exact
path={`/error=:error`}
component={withStudioLayout(Error520)}
/>
<PrivateRoute component={withStudioLayout(Error404)} />
</Switch>
</Suspense>
</Router>
) : (
<Offline />
);
}
}
const mapStateToProps = state => {
return {
settings: state.auth.settings
};
}
};
export default connect(mapStateToProps)(App);
const mapDispatchToProps = dispatch => {
return {
getUser: () => getUser(dispatch),
getPortalSettings: () => getPortalSettings(dispatch),
getModules: () => getModules(dispatch),
getPortalPasswordSettings: () => getPortalPasswordSettings(dispatch),
getPortalCultures: () => getPortalCultures(dispatch),
fetchMyFolder: () => fetchMyFolder(dispatch),
fetchTreeFolders: () => fetchTreeFolders(dispatch),
fetchFiles: (folderId, filter) => fetchFiles(folderId, filter, dispatch),
finalize: () => {
dispatch(setCurrentProductHomePage(config.homepage));
dispatch(setCurrentProductId("e67be73d-f9ae-4ce1-8fec-1880cb518cb4"));
dispatch(setIsLoaded(true));
},
setIsLoaded: () => dispatch(setIsLoaded(true))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);

View File

@ -1,145 +1,15 @@
import "./wdyr";
//import "./wdyr";
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import axios from "axios";
import store from "./store/store";
import {
fetchMyFolder,
fetchTreeFolders,
fetchFiles
} from "./store/files/actions";
import config from "../package.json";
import "./custom.scss";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import {
store as commonStore,
constants,
ErrorBoundary,
api
} from "asc-web-common";
import { getFilterByLocation } from "./helpers/converters";
const {
setIsLoaded,
getUser,
getPortalSettings,
getModules,
setCurrentProductId,
setCurrentProductHomePage,
getPortalPasswordSettings,
getPortalCultures
} = commonStore.auth.actions;
const { AUTH_KEY } = constants;
const { FilesFilter } = api;
const token = localStorage.getItem(AUTH_KEY);
if (token) {
const requests = [
getUser(store.dispatch),
getPortalSettings(store.dispatch),
getModules(store.dispatch),
getPortalPasswordSettings(store.dispatch),
getPortalCultures(store.dispatch),
fetchMyFolder(store.dispatch),
fetchTreeFolders(store.dispatch)
];
axios
.all(requests)
.then(() => {
const reg = new RegExp(`${config.homepage}((/?)$|/filter)`, "gm"); //TODO: Always find?
const match = window.location.pathname.match(reg);
let filterObj = null;
if (match && match.length > 0) {
filterObj = getFilterByLocation(window.location);
if (!filterObj) {
filterObj = FilesFilter.getDefault();
}
}
return Promise.resolve(filterObj);
})
.then(filter => {
let dataObj = filter;
if (filter && filter.authorType) {
const filterObj = filter;
const authorType = filterObj.authorType;
const indexOfUnderscore = authorType.indexOf("_");
const type = authorType.slice(0, indexOfUnderscore);
const itemId = authorType.slice(indexOfUnderscore + 1);
if (itemId) {
dataObj = {
type,
itemId,
filter: filterObj
};
} else {
filterObj.authorType = null;
dataObj = filterObj;
}
}
return Promise.resolve(dataObj);
})
.then(data => {
if (!data) return Promise.resolve();
if (data instanceof FilesFilter) return Promise.resolve(data);
const { filter, itemId, type } = data;
const newFilter = filter ? filter.clone() : FilesFilter.getDefault();
switch (type) {
case "group":
return Promise.all([api.groups.getGroup(itemId), newFilter]);
case "user":
return Promise.all([api.people.getUserById(itemId), newFilter]);
default:
return Promise.resolve(newFilter);
}
})
.catch(err => {
Promise.resolve(FilesFilter.getDefault());
console.warn("Filter restored by default", err);
})
.then(data => {
if (!data) return Promise.resolve();
if (data instanceof FilesFilter) return Promise.resolve(data);
const result = data[0];
const filter = data[1];
const type = result.displayName ? "user" : "group";
const selectedItem = {
key: result.id,
label: type === "user" ? result.displayName : result.name,
type
};
filter.selectedItem = selectedItem;
return Promise.resolve(filter);
})
.then(filter => {
if (!filter) return Promise.resolve();
const folderId = filter.folder;
return fetchFiles(folderId, filter, store.dispatch);
})
.then(() => {
store.dispatch(setCurrentProductHomePage(config.homepage));
store.dispatch(
setCurrentProductId("e67be73d-f9ae-4ce1-8fec-1880cb518cb4")
);
store.dispatch(setIsLoaded(true));
});
} else {
store.dispatch(setIsLoaded(true));
}
import { ErrorBoundary } from "asc-web-common";
ReactDOM.render(
<Provider store={store}>

View File

@ -67,25 +67,25 @@ class App extends React.Component {
const token = localStorage.getItem(AUTH_KEY);
if (token) {
const requests = [
getUser(),
getPortalSettings(),
getModules(),
getPortalPasswordSettings(),
getPortalCultures(),
fetchGroups(),
fetchPeople()
];
axios.all(requests).then(() => {
this.removeLoader();
finalize();
});
} else {
if (!token) {
this.removeLoader();
setIsLoaded();
return setIsLoaded();
}
const requests = [
getUser(),
getPortalSettings(),
getModules(),
getPortalPasswordSettings(),
getPortalCultures(),
fetchGroups(),
fetchPeople()
];
axios.all(requests).then(() => {
this.removeLoader();
finalize();
});
}
render() {