diff --git a/src/renderer/src/components/PackageItem/index.jsx b/src/renderer/src/components/PackageItem/index.jsx index 772a2ac..7bb3a5f 100644 --- a/src/renderer/src/components/PackageItem/index.jsx +++ b/src/renderer/src/components/PackageItem/index.jsx @@ -45,7 +45,7 @@ const PackageItem = (props) => { } const onClickOptions = () => { - app.location.push(`/package/${manifest.id}`) + app.location.push(`/pkg/${manifest.id}`) } const onClickCancelInstall = () => { diff --git a/src/renderer/src/layout/components/Header/index.jsx b/src/renderer/src/layout/components/Header/index.jsx index 9d4329f..3af492f 100644 --- a/src/renderer/src/layout/components/Header/index.jsx +++ b/src/renderer/src/layout/components/Header/index.jsx @@ -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 -
app.location.push("/")}> - -
+ const [decorator, setDecorator] = React.useState({}) + const [navMode, setNavMode] = React.useState(false) - { - !ctx.loading &&
- { - ctx.authorizedServices?.drive && - } + function onChangeAppLocation(_path, state) { + const isNavigable = _path !== "/" - {/* { - ctx.updateText && } - disabled - > - {ctx.updateText} - - } */} + const decorator = PathsDecorators.find((route) => { + const routePath = route.path.replace(/\*/g, ".*").replace(/!/g, "^") - { - ctx.updateAvailable && } - onClick={app.applyUpdate} - type="primary" - > - Update now - - } + return new RegExp(routePath).test(_path) + }) - } - onClick={() => app.location.push("/")} - /> + document.startViewTransition(() => { + setDecorator({}) + setNavMode(isNavigable) - } - onClick={() => app.location.push("/settings")} - /> + if (decorator) { + setDecorator(decorator) + } + }) + } - { - ctx.pkg && - v{ctx.pkg.version} - - } -
+ React.useEffect(() => { + app.location.listen(onChangeAppLocation) + + return () => { + app.location.unlisten(onChangeAppLocation) } -
+ }, []) + + return
+ + { + !navMode && <> +
app.location.push("/")}> + +
+ + { + !ctx.loading &&
+ { + ctx.authorizedServices?.drive && + } + + { + ctx.updateAvailable && } + onClick={app.applyUpdate} + type="primary" + > + Update now + + } + + } + onClick={() => app.location.push("/")} + /> + + } + onClick={() => app.location.push("/settings")} + /> + + { + ctx.pkg && + v{ctx.pkg.version} + + } +
+ } + + } + + { + navMode &&
+
+ { + app.location.back() + }} + /> +
+ +
+

+ { + decorator.label + } +

+
+
+ } + +
+
} export default Header \ No newline at end of file diff --git a/src/renderer/src/layout/components/Header/index.less b/src/renderer/src/layout/components/Header/index.less new file mode 100644 index 0000000..78ac904 --- /dev/null +++ b/src/renderer/src/layout/components/Header/index.less @@ -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; + } + } + + } +} \ No newline at end of file diff --git a/src/renderer/src/pages/manager/index.jsx b/src/renderer/src/pages/index.jsx similarity index 100% rename from src/renderer/src/pages/manager/index.jsx rename to src/renderer/src/pages/index.jsx diff --git a/src/renderer/src/pages/manager/index.less b/src/renderer/src/pages/index.less similarity index 100% rename from src/renderer/src/pages/manager/index.less rename to src/renderer/src/pages/index.less diff --git a/src/renderer/src/pages/pkg/index.jsx b/src/renderer/src/pages/pkg/[pkg_id].jsx similarity index 95% rename from src/renderer/src/pages/pkg/index.jsx rename to src/renderer/src/pages/pkg/[pkg_id].jsx index 47c1813..f9f25bc 100644 --- a/src/renderer/src/pages/pkg/index.jsx +++ b/src/renderer/src/pages/pkg/[pkg_id].jsx @@ -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
-
-
- { - app.location.push("/") - }} - /> - Back -
-
- { const Settings = () => { return
-
-
- { - app.location.push("/") - }} - /> - Back -
- -
- -

Settings

-
-
-
-
} diff --git a/src/renderer/src/router.jsx b/src/renderer/src/router.jsx index ceb2dc6..ae5e2eb 100644 --- a/src/renderer/src/router.jsx +++ b/src/renderer/src/router.jsx @@ -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
Not found
+} + +const DefaultLoadingRender = () => { + return +} + +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 + } + return props.children } @@ -60,7 +118,31 @@ export const InternalRouter = (props) => { } -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.map((route, index) => { + return + }) + } + + } \ No newline at end of file diff --git a/src/renderer/src/style/index.less b/src/renderer/src/style/index.less index 11ceb17..5a729c1 100644 --- a/src/renderer/src/style/index.less +++ b/src/renderer/src/style/index.less @@ -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%; diff --git a/src/renderer/src/style/vars.less b/src/renderer/src/style/vars.less index 7392455..c29878d 100644 --- a/src/renderer/src/style/vars.less +++ b/src/renderer/src/style/vars.less @@ -2,4 +2,7 @@ @var-background-color-primary: #424549; @var-background-color-secondary: #1e2124; @var-primary-color: #36d7b7; //#F3B61F; -@var-border-color: #a1a2a2; \ No newline at end of file +@var-border-color: #a1a2a2; + +@var-app_header_height: 64px; +@var-app_global_padding: 20px; \ No newline at end of file