文件
mynode/packages/app-desktop/gui/Navigator.tsx
chen 301194963b
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
feat: Initial clean release of CHM
2026-11-04 11:29:39 +08:00

122 行
3.6 KiB
TypeScript

import * as React from 'react';
import { connect } from 'react-redux';
import Setting from '@joplin/lib/models/Setting';
import { AppState, AppStateRoute } from '../app.reducer';
import bridge from '../services/bridge';
import { useContext, useEffect, useMemo, useRef, useState } from 'react';
import { WindowIdContext } from './NewWindowOrIFrame';
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Each registered screen has its own props shape; the navigator just spreads them through
type ScreenProps = any;
interface AppScreen {
screen: React.ComponentType<ScreenProps>;
title?: ()=> string;
}
interface Props {
route: AppStateRoute;
screens: Record<string, AppScreen>;
style: React.CSSProperties;
className?: string;
}
const useWindowTitleManager = (screenInfo: AppScreen) => {
const windowTitle = useMemo(() => {
const devMarker = Setting.value('env') === 'dev' ? ` (DEV - ${Setting.value('profileDir')})` : '';
const windowTitle = [`CHM${devMarker}`];
if (screenInfo?.title) {
windowTitle.push(screenInfo.title());
}
return windowTitle.join(' - ');
}, [screenInfo]);
const windowId = useContext(WindowIdContext);
useEffect(() => {
bridge().windowById(windowId)?.setTitle(windowTitle);
}, [windowTitle, windowId]);
};
const useWindowRefocusManager = (route: AppStateRoute) => {
const windowId = useContext(WindowIdContext);
const prevRouteName = useRef<string|null>(null);
const routeName = route?.routeName;
useEffect(() => {
// When a navigation happens in an unfocused window, show the window to the user.
// This might happen if, for example, a secondary window triggers a navigation in
// the main window.
if (routeName && routeName !== prevRouteName.current) {
bridge().switchToWindow(windowId);
}
prevRouteName.current = routeName;
}, [routeName, windowId]);
};
const useContainerSize = (container: HTMLElement|null) => {
const [size, setSize] = useState({
// Show the container as soon as possible: Default to the window size,
// which is usually correct:
width: container?.clientWidth ?? window.innerWidth,
height: container?.clientHeight ?? window.innerHeight,
});
const currentSizeRef = useRef(size);
currentSizeRef.current = size;
useEffect(() => {
if (!container) return () => {};
const updateSizeIfDifferent = () => {
const { width: lastWidth, height: lastHeight } = currentSizeRef.current;
if (lastWidth !== container.clientWidth || lastHeight !== container.clientHeight) {
setSize({
width: container.clientWidth,
height: container.clientHeight,
});
}
};
// Ensure that the initial size is set, even if the ResizeObserver doesn't run the callback initially
updateSizeIfDifferent();
const observer = new ResizeObserver(updateSizeIfDifferent);
observer.observe(container);
return () => {
observer.disconnect();
};
}, [container]);
return size;
};
const NavigatorComponent: React.FC<Props> = props => {
const route = props.route;
const screenInfo = props.screens[route?.routeName];
const [container, setContainer] = useState<HTMLElement|null>(null);
useWindowTitleManager(screenInfo);
useWindowRefocusManager(route);
const size = useContainerSize(container);
if (!route) throw new Error('Route must not be null');
const screenProps = route.props ? route.props : {};
const Screen = screenInfo.screen;
return (
<div ref={setContainer} style={props.style} className={props.className}>
<Screen style={size} {...screenProps} />
</div>
);
};
const Navigator = connect((state: AppState) => {
return {
route: state.route,
};
})(NavigatorComponent);
export default Navigator;