mirror of
https://github.com/ragestudio/relic.git
synced 2025-06-09 02:24:18 +00:00
use new router model
This commit is contained in:
parent
fbb87e46d1
commit
4ab0bc5975
@ -45,7 +45,7 @@ const PackageItem = (props) => {
|
||||
}
|
||||
|
||||
const onClickOptions = () => {
|
||||
app.location.push(`/package/${manifest.id}`)
|
||||
app.location.push(`/pkg/${manifest.id}`)
|
||||
}
|
||||
|
||||
const onClickCancelInstall = () => {
|
||||
|
@ -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
|
90
src/renderer/src/layout/components/Header/index.less
Normal file
90
src/renderer/src/layout/components/Header/index.less
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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}
|
@ -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 {
|
||||
|
@ -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>
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
}
|
@ -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%;
|
||||
|
@ -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;
|
Loading…
x
Reference in New Issue
Block a user