use new router model

This commit is contained in:
SrGooglo 2024-02-16 01:39:00 +01:00
parent fbb87e46d1
commit 4ab0bc5975
11 changed files with 311 additions and 164 deletions

View File

@ -45,7 +45,7 @@ const PackageItem = (props) => {
}
const onClickOptions = () => {
app.location.push(`/package/${manifest.id}`)
app.location.push(`/pkg/${manifest.id}`)
}
const onClickCancelInstall = () => {

View File

@ -2,69 +2,122 @@ import React from "react"
import * as antd from "antd"
import { Icons } from "components/Icons"
import PathsDecorators from "config/paths_decorators"
import GlobalStateContext from "contexts/global"
import Icon from "../../../../assets/icon"
import "./index.less"
const Header = (props) => {
const ctx = React.useContext(GlobalStateContext)
return <antd.Layout.Header className="app_header">
<div className="branding" onClick={() => app.location.push("/")}>
<Icon />
</div>
const [decorator, setDecorator] = React.useState({})
const [navMode, setNavMode] = React.useState(false)
{
!ctx.loading && <div className="menu">
{
ctx.authorizedServices?.drive && <Icons.SiGoogledrive
style={{
color: `var(--primary-color)`,
}}
/>
}
function onChangeAppLocation(_path, state) {
const isNavigable = _path !== "/"
{/* {
ctx.updateText && <antd.Button
size="small"
icon={<Icons.MdRefresh />}
disabled
>
{ctx.updateText}
</antd.Button>
} */}
const decorator = PathsDecorators.find((route) => {
const routePath = route.path.replace(/\*/g, ".*").replace(/!/g, "^")
{
ctx.updateAvailable && <antd.Button
size="small"
icon={<Icons.MdDownload />}
onClick={app.applyUpdate}
type="primary"
>
Update now
</antd.Button>
}
return new RegExp(routePath).test(_path)
})
<antd.Button
size="small"
icon={<Icons.MdHome />}
onClick={() => app.location.push("/")}
/>
document.startViewTransition(() => {
setDecorator({})
setNavMode(isNavigable)
<antd.Button
size="small"
icon={<Icons.MdSettings />}
onClick={() => app.location.push("/settings")}
/>
if (decorator) {
setDecorator(decorator)
}
})
}
{
ctx.pkg && <antd.Tag>
v{ctx.pkg.version}
</antd.Tag>
}
</div>
React.useEffect(() => {
app.location.listen(onChangeAppLocation)
return () => {
app.location.unlisten(onChangeAppLocation)
}
</antd.Layout.Header>
}, [])
return <div className="app_header_wrapper">
<antd.Layout.Header className="app_header">
{
!navMode && <>
<div className="branding" onClick={() => app.location.push("/")}>
<Icon />
</div>
{
!ctx.loading && <div className="menu">
{
ctx.authorizedServices?.drive && <Icons.SiGoogledrive
style={{
color: `var(--primary-color)`,
}}
/>
}
{
ctx.updateAvailable && <antd.Button
size="small"
icon={<Icons.MdDownload />}
onClick={app.applyUpdate}
type="primary"
>
Update now
</antd.Button>
}
<antd.Button
size="small"
icon={<Icons.MdHome />}
onClick={() => app.location.push("/")}
/>
<antd.Button
size="small"
icon={<Icons.MdSettings />}
onClick={() => app.location.push("/settings")}
/>
{
ctx.pkg && <antd.Tag>
v{ctx.pkg.version}
</antd.Tag>
}
</div>
}
</>
}
{
navMode && <div className="app_header_nav">
<div className="app_header_nav_back">
<Icons.MdChevronLeft
onClick={() => {
app.location.back()
}}
/>
</div>
<div
className="app_header_nav_title"
>
<h1>
{
decorator.label
}
</h1>
</div>
</div>
}
</antd.Layout.Header>
</div>
}
export default Header

View File

@ -0,0 +1,90 @@
@import "style/vars.less";
.app_header_wrapper {
z-index: 1500;
display: flex;
flex-direction: column;
}
.app_header {
z-index: 1000;
view-transition-name: main-header;
display: inline-flex;
flex-direction: row;
align-items: center;
background-color: darken(@var-background-color-primary, 10%);
gap: 30px;
height: var(--app_header_height);
padding: 0 20px;
.branding {
display: inline-flex;
flex-direction: row;
align-items: center;
gap: 10px;
svg {
color: var(--primary-color);
height: 40px;
width: 40px;
}
}
.menu {
display: flex;
flex-direction: row;
width: 100%;
align-items: center;
justify-content: flex-end;
gap: 15px;
}
.app_header_nav {
display: inline-flex;
align-items: center;
gap: 15px;
font-size: 1.2rem;
width: 100%;
.app_header_nav_back {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: var(--primary-color);
font-size: 1.4rem;
}
.app_header_nav_title {
view-transition-name: main-header-text;
h1 {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 5px;
}
}
}
}

View File

@ -264,7 +264,7 @@ const PackageOptions = (props) => {
}
const PackageOptionsLoader = (props) => {
const { pkg_id } = useParams()
const { pkg_id } = props.params
const [manifest, setManifest] = React.useState(null)
React.useEffect(() => {
@ -279,17 +279,6 @@ const PackageOptionsLoader = (props) => {
}
return <div className="package_options-wrapper">
<div className="package_options-wrapper-header">
<div className="package_options-wrapper-header-back">
<Icons.MdChevronLeft
onClick={() => {
app.location.push("/")
}}
/>
Back
</div>
</div>
<PackageOptions
manifest={manifest}
{...props}

View File

@ -3,33 +3,6 @@
flex-direction: column;
gap: 10px;
.package_options-wrapper-header {
display: flex;
flex-direction: row;
gap: 20px;
.package_options-wrapper-header-back {
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
svg {
color: var(--primary-color);
border: 1px solid var(--primary-color);
border-radius: 100%;
cursor: pointer;
font-size: 1.5rem;
}
}
}
}
.package_options {

View File

@ -154,28 +154,11 @@ const SettingsList = ({ settings }) => {
const Settings = () => {
return <div className="app_settings">
<div className="app_settings-header">
<div className="app_settings-header-back">
<Icons.MdChevronLeft
onClick={() => {
app.location.push("/")
}}
/>
Back
</div>
<div className="app_settings-header-title">
<Icons.MdSettings />
<h1>Settings</h1>
</div>
</div>
<div className="app_settings-list">
<SettingsList
settings={settingsList}
/>
</div>
</div>
}

View File

@ -1,54 +1,112 @@
import React from "react"
import BarLoader from "react-spinners/BarLoader"
import { HashRouter, Route, Routes, useNavigate } from "react-router-dom"
import { Skeleton } from "antd"
import { HashRouter, Route, Routes, useNavigate, useParams } from "react-router-dom"
import loadable from "@loadable/component"
import GlobalStateContext from "contexts/global"
import PackagesMangerPage from "pages/manager"
import SettingsPage from "pages/settings"
import PackageOptionsPage from "pages/pkg"
const DefaultNotFoundRender = () => {
return <div>Not found</div>
}
const DefaultLoadingRender = () => {
return <Skeleton active />
}
const BuildPageController = (route, element, bindProps) => {
return React.createElement((props) => {
const params = useParams()
const url = new URL(window.location)
const query = new Proxy(url, {
get: (target, prop) => target.searchParams.get(prop),
})
route = route.replace(/\?.+$/, "").replace(/\/{2,}/g, "/")
route = route.replace(/\/$/, "")
return React.createElement(
loadable(element, {
fallback: React.createElement(DefaultLoadingRender),
}),
{
...props,
...bindProps,
url: url,
params: params,
query: query,
},
)
})
}
const NavigationController = (props) => {
if (!app.location) {
app.location = Object()
}
const [initialized, setInitialized] = React.useState(false)
const navigate = useNavigate()
async function setLocation(to, state = {}) {
// clean double slashes
to = to.replace(/\/{2,}/g, "/")
// if state is a number, it's a delay
if (typeof state !== "object") {
state = {}
}
for (const listener of app.location.listeners) {
if (typeof listener === "function") {
listener(to, state)
}
}
app.location.path = to
app.location.last = window.location
return navigate(to, {
state
document.startViewTransition(() => {
navigate(to, {
state
})
})
}
async function backLocation() {
app.location.last = window.location
return setLocation(app.location.last.pathname + app.location.last.search, app.location.last.state)
}
if (transitionDuration >= 100) {
await new Promise((resolve) => setTimeout(resolve, transitionDuration))
}
function pushToListeners(listener) {
app.location.listeners.push(listener)
}
return window.history.back()
function removeFromListeners(listener) {
app.location.listeners = app.location.listeners.filter((item) => {
return item !== listener
})
}
React.useEffect(() => {
app.location = {
last: window.location,
path: "/",
listeners: [],
listen: pushToListeners,
unlisten: removeFromListeners,
push: setLocation,
back: backLocation,
}
setInitialized(true)
}, [])
if (!initialized) {
return <Skeleton />
}
return props.children
}
@ -60,7 +118,31 @@ export const InternalRouter = (props) => {
</HashRouter>
}
export const PageRender = () => {
export const PageRender = (props) => {
const routes = React.useMemo(() => {
let paths = {
...import.meta.glob("/src/pages/**/[a-z[]*.jsx"),
...import.meta.glob("/src/pages/**/[a-z[]*.tsx"),
}
paths = Object.keys(paths).map((route) => {
let path = route
.replace(/\/src\/pages|index|\.jsx$/g, "")
.replace(/\/src\/pages|index|\.tsx$/g, "")
.replace(/\/src\/pages|index|\.mobile|\.jsx$/g, "")
.replace(/\/src\/pages|index|\.mobile|\.tsx$/g, "")
path = path.replace(/\[\.{3}.+\]/, "*").replace(/\[(.+)\]/, ":$1")
return {
path,
element: paths[route],
}
})
return paths
}, [])
const globalState = React.useContext(GlobalStateContext)
if (globalState.initializing_text && globalState.loading) {
@ -79,8 +161,20 @@ export const PageRender = () => {
}
return <Routes>
<Route exact path="/" element={<PackagesMangerPage />} />
<Route exact path="/settings" element={<SettingsPage />} />
<Route exact path="/package/:pkg_id" element={<PackageOptionsPage />} />
{
routes.map((route, index) => {
return <Route
key={index}
path={route.path}
element={BuildPageController(route.path, route.element, props)}
exact
/>
})
}
<Route
path="*"
element={React.createElement(DefaultNotFoundRender)}
/>
</Routes>
}

View File

@ -10,6 +10,9 @@
--primary-color: @var-primary-color;
--text-color: @var-text-color;
--border-color: @var-border-color;
--app_header_height: @var-app_header_height;
--app_global_padding: @var-app_global_padding;
}
html,
@ -47,47 +50,6 @@ body {
background-color: var(--background-color-primary);
}
.app_header {
display: inline-flex;
flex-direction: row;
align-items: center;
background-color: darken(@var-background-color-primary, 10%);
gap: 30px;
padding: 0 20px;
.branding {
display: inline-flex;
flex-direction: row;
align-items: center;
gap: 10px;
svg {
color: var(--primary-color);
height: 40px;
width: 40px;
}
}
.menu {
display: flex;
flex-direction: row;
width: 100%;
align-items: center;
justify-content: flex-end;
gap: 15px;
}
}
.app_content {
width: 100%;
height: 100%;

View File

@ -2,4 +2,7 @@
@var-background-color-primary: #424549;
@var-background-color-secondary: #1e2124;
@var-primary-color: #36d7b7; //#F3B61F;
@var-border-color: #a1a2a2;
@var-border-color: #a1a2a2;
@var-app_header_height: 64px;
@var-app_global_padding: 20px;