improve router

This commit is contained in:
SrGooglo 2023-01-12 20:23:59 +00:00
parent 7369126c65
commit f09f695b46
4 changed files with 109 additions and 179 deletions

View File

@ -45,13 +45,12 @@
"electron-is": "^3.0.0", "electron-is": "^3.0.0",
"electron-log": "^4.4.8", "electron-log": "^4.4.8",
"electron-squirrel-startup": "^1.0.0", "electron-squirrel-startup": "^1.0.0",
"evite": "0.13.7", "evite": "0.13.9",
"fast-average-color": "^9.2.0", "fast-average-color": "^9.2.0",
"faye": "1.4.0", "faye": "1.4.0",
"feather-reactjs": "2.0.13", "feather-reactjs": "2.0.13",
"fuse.js": "6.5.3", "fuse.js": "6.5.3",
"global": "4.4.0", "global": "4.4.0",
"history": "5.2.0",
"hls.js": "^1.1.5", "hls.js": "^1.1.5",
"howler": "2.2.3", "howler": "2.2.3",
"i18next": "21.6.6", "i18next": "21.6.6",
@ -59,7 +58,9 @@
"jwt-decode": "3.1.2", "jwt-decode": "3.1.2",
"less": "4.1.2", "less": "4.1.2",
"linebridge": "0.13.0", "linebridge": "0.13.0",
"localforage": "^1.10.0",
"luxon": "^3.0.4", "luxon": "^3.0.4",
"match-sorter": "^6.3.1",
"moment": "2.29.4", "moment": "2.29.4",
"moment-timezone": "^0.5.37", "moment-timezone": "^0.5.37",
"mpegts.js": "^1.6.10", "mpegts.js": "^1.6.10",
@ -89,10 +90,12 @@
"react-responsive-carousel": "^3.2.23", "react-responsive-carousel": "^3.2.23",
"react-reveal": "1.2.2", "react-reveal": "1.2.2",
"react-rnd": "10.3.5", "react-rnd": "10.3.5",
"react-router-dom": "^6.6.2",
"react-ticker": "^1.3.2", "react-ticker": "^1.3.2",
"react-transition-group": "^4.4.5", "react-transition-group": "^4.4.5",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"rxjs": "^7.5.5", "rxjs": "^7.5.5",
"sort-by": "^1.2.0",
"store": "^2.0.12", "store": "^2.0.12",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"wait-on": "^6.0.1" "wait-on": "^6.0.1"

View File

@ -53,6 +53,7 @@ import { EviteRuntime } from "evite"
import { Helmet } from "react-helmet" import { Helmet } from "react-helmet"
import * as antd from "antd" import * as antd from "antd"
import { Toast } from "antd-mobile" import { Toast } from "antd-mobile"
import { BrowserRouter } from "react-router-dom"
import { StatusBar, Style } from "@capacitor/status-bar" import { StatusBar, Style } from "@capacitor/status-bar"
import { App as CapacitorApp } from "@capacitor/app" import { App as CapacitorApp } from "@capacitor/app"
import { Translation } from "react-i18next" import { Translation } from "react-i18next"
@ -455,15 +456,14 @@ class App extends React.Component {
}) })
StatusBar.setOverlaysWebView({ overlay: true }) StatusBar.setOverlaysWebView({ overlay: true })
//window.app.hideStatusBar()
CapacitorApp.addListener('backButton', ({ canGoBack }) => { CapacitorApp.addListener("backButton", ({ canGoBack }) => {
if (!canGoBack) { if (!canGoBack) {
CapacitorApp.exitApp(); CapacitorApp.exitApp()
} else { } else {
window.history.back(); window.history.back()
} }
}); })
} }
const userAgentPlatform = window.navigator.userAgent.toLowerCase() const userAgentPlatform = window.navigator.userAgent.toLowerCase()
@ -532,6 +532,7 @@ class App extends React.Component {
async () => { async () => {
try { try {
await this.__SessionInit() await this.__SessionInit()
await this.__UserInit()
app.eventBus.emit("app.initialization.session_success") app.eventBus.emit("app.initialization.session_success")
} catch (error) { } catch (error) {
@ -543,20 +544,6 @@ class App extends React.Component {
} }
} }
}, },
async () => {
try {
await this.__UserInit()
app.eventBus.emit("app.initialization.user_success")
} catch (error) {
app.eventBus.emit("app.initialization.user_error", error)
throw {
cause: "Cannot initialize user data",
details: error.message,
}
}
},
] ]
await Promise.tasked(initializationTasks).catch((reason) => { await Promise.tasked(initializationTasks).catch((reason) => {
@ -607,8 +594,8 @@ class App extends React.Component {
<meta name="og:description" content={config.app.siteDescription} /> <meta name="og:description" content={config.app.siteDescription} />
<meta property="og:title" content={config.app.siteName} /> <meta property="og:title" content={config.app.siteName} />
</Helmet> </Helmet>
<antd.ConfigProvider> <BrowserRouter>
<Router.InternalRouter> <antd.ConfigProvider>
<Layout <Layout
user={this.state.user} user={this.state.user}
staticRenders={App.staticRenders} staticRenders={App.staticRenders}
@ -622,8 +609,8 @@ class App extends React.Component {
> >
<Router.PageRender /> <Router.PageRender />
</Layout> </Layout>
</Router.InternalRouter> </antd.ConfigProvider>
</antd.ConfigProvider> </BrowserRouter>
</React.Fragment> </React.Fragment>
} }
} }

View File

@ -54,35 +54,8 @@ export default class Layout extends React.PureComponent {
document.querySelector("#transitionLayer").classList.add("fade-opacity-leave") document.querySelector("#transitionLayer").classList.add("fade-opacity-leave")
}, },
"router.transitionStart": () => { "router.navigate": (path, options) => {
this.progressBar.start() this.makePageTransition(path, options)
if (!app.settings.get("reduceAnimations")) {
// add "fade-transverse-leave" class to `transitionLayer`
const transitionLayer = document.getElementById("transitionLayer")
if (!transitionLayer) {
console.warn("transitionLayer not found, no animation will be played")
return
}
transitionLayer.classList.add("fade-transverse-leave")
}
},
"router.transitionFinish": () => {
this.progressBar.done()
if (!app.settings.get("reduceAnimations")) {
// remove "fade-transverse-leave" class to `transitionLayer`
const transitionLayer = document.getElementById("transitionLayer")
if (!transitionLayer) {
console.warn("transitionLayer not found, no animation will be played")
return
}
transitionLayer.classList.remove("fade-transverse-leave")
}
}, },
} }
@ -112,6 +85,31 @@ export default class Layout extends React.PureComponent {
this.setState({ renderError: { info, stack } }) this.setState({ renderError: { info, stack } })
} }
makePageTransition(path, options = {}) {
this.progressBar.start()
if (app.settings.get("reduceAnimations") || options.state.noTransition) {
this.progressBar.done()
return false
}
const transitionLayer = document.getElementById("transitionLayer")
if (!transitionLayer) {
console.warn("transitionLayer not found, no animation will be played")
return false
}
transitionLayer.classList.add("fade-transverse-leave")
setTimeout(() => {
this.progressBar.done()
transitionLayer.classList.remove("fade-transverse-leave")
}, options.state.transitionDelay ?? 250)
}
setLayout = (layout) => { setLayout = (layout) => {
if (typeof Layouts[layout] === "function") { if (typeof Layouts[layout] === "function") {
return this.setState({ return this.setState({

View File

@ -1,12 +1,17 @@
import React from "react" import React from "react"
import { Switch, Route, BrowserRouter, withRouter } from "react-router-dom" import { Route, Routes, Navigate, useLocation, useNavigate, useParams } from "react-router-dom"
import { Skeleton } from "antd"
import loadable from "@loadable/component"
const NotFoundRender = () => { const NotFoundRender = () => {
return <div>Not found</div> return <div>Not found</div>
} }
const LoadingRender = () => {
return <Skeleton active />
}
const paths = { const paths = {
...import.meta.glob("/src/debug/components/**/[a-z[]*.jsx"),
...import.meta.glob("/src/pages/**/[a-z[]*.jsx"), ...import.meta.glob("/src/pages/**/[a-z[]*.jsx"),
...import.meta.glob("/src/pages/**/[a-z[]*.tsx"), ...import.meta.glob("/src/pages/**/[a-z[]*.tsx"),
} }
@ -16,166 +21,103 @@ const pathsMobile = {
...import.meta.glob("/src/pages/**/[a-z[]*.mobile.tsx"), ...import.meta.glob("/src/pages/**/[a-z[]*.mobile.tsx"),
} }
function generateElementWrapper(route, element, bindProps) {
return React.createElement((props) => {
const params = useParams()
return React.createElement(
loadable(element, {
fallback: React.createElement(LoadingRender),
}),
{
...props,
...bindProps,
params: params,
})
})
}
const routes = Object.keys(paths).map((route) => { const routes = Object.keys(paths).map((route) => {
const path = route const path = route
.replace(/\/src\/pages|index|\.jsx$/g, "") .replace(/\/src\/pages|index|\.jsx$/g, "")
.replace(/\/src\/pages|index|\.tsx$/g, "") .replace(/\/src\/pages|index|\.tsx$/g, "")
.replace(/\/src\/debug\/components/g, "/debug")
.replace(/\[\.{3}.+\]/, "*") .replace(/\[\.{3}.+\]/, "*")
.replace(/\[(.+)\]/, ":$1") .replace(/\[(.+)\]/, ":$1")
return { path, component: React.lazy(paths[route]) } return {
path,
element: paths[route]
}
}) })
const mobileComponents = Object.fromEntries(Object.keys(pathsMobile).map((route) => { const mobileRoutes = Object.keys(pathsMobile).map((route) => {
const path = route const path = route
.replace(/\/src\/pages|index|\.mobile|\.jsx$/g, "") .replace(/\/src\/pages|index|\.mobile|\.jsx$/g, "")
.replace(/\/src\/pages|index|\.mobile|\.tsx$/g, "") .replace(/\/src\/pages|index|\.mobile|\.tsx$/g, "")
.replace(/\/src\/debug\/components/g, "/debug")
.replace(/\[\.{3}.+\]/, "*") .replace(/\[\.{3}.+\]/, "*")
.replace(/\[(.+)\]/, ":$1") .replace(/\[(.+)\]/, ":$1")
return [path, React.lazy(pathsMobile[route])] return {
})) path,
element: pathsMobile[path]
export function BindContexts(component) {
let contexts = {
main: {},
app: {},
} }
})
if (typeof component.bindApp === "string") { export const PageRender = React.memo((props) => {
if (component.bindApp === "all") { const navigate = useNavigate()
Object.keys(app).forEach((key) => { app.location = useLocation()
contexts.app[key] = app[key]
})
}
} else {
if (Array.isArray(component.bindApp)) {
component.bindApp.forEach((key) => {
contexts.app[key] = app[key]
})
}
}
if (typeof component.bindMain === "string") {
if (component.bindMain === "all") {
Object.keys(main).forEach((key) => {
contexts.main[key] = main[key]
})
}
} else {
if (Array.isArray(component.bindMain)) {
component.bindMain.forEach((key) => {
contexts.main[key] = main[key]
})
}
}
return (props) => React.createElement(component, { ...props, contexts })
}
export const Router = withRouter((props) => {
const defaultTransitionDelay = 150
const forceUpdate = React.useReducer(() => ({}))[1]
React.useEffect(() => { React.useEffect(() => {
props.history.listen((event) => { app.setLocation = async (to, state = {}) => {
if (typeof props.onTransitionFinish === "function") {
props.onTransitionFinish(event)
}
window.app.eventBus.emit("router.transitionFinish", event)
})
props.history.setLocation = (to, state = {}, delay = 150) => {
// clean double slashes // clean double slashes
to = to.replace(/\/{2,}/g, "/") to = to.replace(/\/{2,}/g, "/")
// if state is a number, it's a delay // if state is a number, it's a delay
if (typeof state !== "object") { if (typeof state !== "object") {
delay = state
state = {} state = {}
} }
const lastLocation = props.history.lastLocation state.transitionDelay = Number((app.style.getValue("page-transition-duration") ?? "250ms").replace("ms", ""))
if (typeof lastLocation !== "undefined" && lastLocation?.pathname === to && lastLocation?.state === state) { app.eventBus.emit("router.navigate", to, {
return false state,
})
if (state.transitionDelay >= 100) {
await new Promise((resolve) => {
setTimeout(() => {
resolve()
}, state.transitionDelay)
})
} }
if (typeof props.onTransitionStart === "function") { return navigate(to, {
props.onTransitionStart(delay) state
} })
window.app.eventBus.emit("router.transitionStart", delay)
setTimeout(() => {
props.history.push({
pathname: to,
}, state)
props.history.lastLocation = window.location
}, delay ?? defaultTransitionDelay)
} }
window.app.eventBus.on(`router.forceUpdate`, forceUpdate)
props.history.lastLocation = window.location
window.app.setLocation = props.history.setLocation
}, []) }, [])
const router = { return <Routes>
history: props.history, {
lastLocation: props.history.lastLocation, routes.map((route, index) => {
forceUpdate, let Element = route.element
}
// return children with router in props
return React.cloneElement(props.children, { router })
})
export const InternalRouter = (props) => {
return <BrowserRouter>
<Router {...props} />
</BrowserRouter>
}
export const PageRender = (props) => {
return <React.Suspense fallback={props.staticRenders?.PageLoad ? React.createElement(props.staticRenders?.PageLoad) : "Loading..."}>
<Switch>
{routes.map(({ path, component: Component = React.Fragment }) => {
if (window.isMobile) { if (window.isMobile) {
if (mobileComponents[path]) { const mobileElement = mobileRoutes.find((r) => r.path === route.path)
Component = mobileComponents[path]
} else { if (mobileElement) {
console.warn(`No mobile component for ${path}, using default`) Element = mobileElement
} }
} }
const generateRenderComponent = (props) => {
return React.createElement(BindContexts(Component), {
...props,
history: props.history,
})
}
return <Route return <Route
key={path} key={index}
path={path} path={route.path}
component={generateRenderComponent} element={generateElementWrapper(route.path, route.element, props)}
exact={true} exact
/> />
})} })
<Route path="*" component={props.staticRenders?.NotFound ?? NotFoundRender} /> }
</Switch> <Route path="*" element={React.createElement(props.staticRenders?.NotFound) ?? <NotFoundRender />} />
</React.Suspense> </Routes>
} })
export default {
routes,
BindContexts,
InternalRouter,
PageRender,
}