Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve #135

Merged
merged 6 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/cli/src/commands/start_server/middlewares/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default (ctx: Context & I18nMiddleware, next: Next) => {
let lang: Language | null = null;
const getLang = () => {
if (!lang) {
// @ts-expect-error
// @ts-expect-error: known types
({ __lang: lang } = ctx.query as { [CommonQuery.LANGUAGE]: unknown });
if (!lang || !ACCEPT_LANGUAGES.includes(lang)) {
const negotiator = new Negotiator(ctx.request);
Expand Down
4 changes: 2 additions & 2 deletions apps/pwa/src/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ import { ThemeProvider } from 'styled-components';
import { HashRouter } from 'react-router-dom';
import App from './app';
import UncaughtError from './uncaught_error';
import theme from '../global_states/theme';
import Head from './head';
import { useTheme } from '@/global_states/theme';

const fallback = (error: Error) => <UncaughtError error={error} />;

function Wrapper() {
return (
<ErrorBoundary fallback={fallback}>
<HashRouter>
<ThemeProvider theme={theme.useState()}>
<ThemeProvider theme={useTheme()}>
<Head />
<App />
<GlobalStyle />
Expand Down
80 changes: 41 additions & 39 deletions apps/pwa/src/components/slider/slider.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { CSSVariable } from '@/global_style';
import {
HtmlHTMLAttributes,
PointerEvent,
PointerEvent as ReactPointerEvent,
PointerEventHandler,
ReactNode,
useEffect,
useRef,
useState,
useCallback,
useRef,
} from 'react';
import styled from 'styled-components';
import classnames from 'classnames';
Expand Down Expand Up @@ -62,15 +63,6 @@ const Style = styled.div`
}
}
`;
const getPointerEventRelativePercent = (
event: PointerEvent<HTMLDivElement>,
) => {
const target = event.currentTarget as HTMLDivElement;
const rect = target.getBoundingClientRect();
const x = event.clientX - rect.left;
const percent = x / target.clientWidth;
return Math.max(Math.min(percent, 1), 0);
};

function Slider({
edge = Edge.ROUNDED,
Expand All @@ -87,51 +79,63 @@ function Slider({
max?: number;
secondTrack?: ReactNode;
}) {
const pointerDownRef = useRef(false);
const ref = useRef<HTMLDivElement>(null);
const getPointerEventRelativePercent = useCallback(
(event: ReactPointerEvent | PointerEvent) => {
const rect = ref.current!.getBoundingClientRect();
const x = event.clientX - rect.left;
const percent = x / ref.current!.clientWidth;
return Math.max(Math.min(percent, 1), 0);
},
[],
);

const [pointerDown, setPointerDown] = useState(false);
const [shadowPercent, setShadowPercent] = useState<number | undefined>(
undefined,
);

const onPointerDown: PointerEventHandler<HTMLDivElement> = (e) => {
e.preventDefault();
(e.currentTarget as HTMLDivElement).setPointerCapture(e.pointerId);

pointerDownRef.current = true;
setPointerDown(true);

const percent = getPointerEventRelativePercent(e);
setShadowPercent(percent);
};
const onPointerMove: PointerEventHandler<HTMLDivElement> = (e) => {
e.preventDefault();
if (pointerDownRef.current) {
const percent = getPointerEventRelativePercent(e);
setShadowPercent(percent);
}
return setShadowPercent(percent);
};
const onPointerUp: PointerEventHandler<HTMLDivElement> = (e) => {
pointerDownRef.current = false;

const percent = getPointerEventRelativePercent(e);
// eslint-disable-next-line no-unused-expressions
onChange && onChange(max * percent);

window.setTimeout(() => setShadowPercent(undefined), 0);
};
useEffect(() => {
if (pointerDown) {
const onPointerMove = (e: PointerEvent) => {
const percent = getPointerEventRelativePercent(e);
return setShadowPercent(percent);
};
document.addEventListener('pointermove', onPointerMove);
return () => document.removeEventListener('pointermove', onPointerMove);
}
}, [getPointerEventRelativePercent, pointerDown]);

useEffect(() => {
const onLeaveDocument = () => {
pointerDownRef.current = false;
window.setTimeout(() => setShadowPercent(undefined), 0);
};
document.addEventListener('mouseleave', onLeaveDocument);
return () => document.removeEventListener('mouseleave', onLeaveDocument);
}, []);
if (pointerDown) {
const onPointerUp = (e: PointerEvent) => {
setPointerDown(false);

const percent = getPointerEventRelativePercent(e);
// eslint-disable-next-line no-unused-expressions
onChange && onChange(max * percent);

return globalThis.setTimeout(() => setShadowPercent(undefined), 0);
};
document.addEventListener('pointerup', onPointerUp);
return () => document.removeEventListener('pointerup', onPointerUp);
}
}, [getPointerEventRelativePercent, max, onChange, pointerDown]);

const actualPercent = shadowPercent ?? current / max;
return (
<Style
{...props}
ref={ref}
className={classnames(
{
untouchable: !IS_TOUCHABLE,
Expand All @@ -140,8 +144,6 @@ function Slider({
className,
)}
onPointerDown={onPointerDown}
onPointerUp={onPointerUp}
onPointerMove={onPointerMove}
>
{secondTrack}
<div
Expand Down
57 changes: 22 additions & 35 deletions apps/pwa/src/global_states/server.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import XState from '@/utils/x_state';
import logger from '@/utils/logger';
import storage, { Key } from '@/storage';
import { type Server, type ServerState } from '@/constants/server';
import globalEventemitter, { EventType } from '@/platform/global_eventemitter';
import definition from '@/definition';
import { BETA_VERSION_START } from '#/constants';
import { create } from 'zustand';

export function getSelectedServer(ss: ServerState) {
return ss.selectedServerOrigin
Expand All @@ -19,36 +18,27 @@ export function getSelectedUser(server: Server) {
}

const initialServerList = await storage.getItem(Key.SERVER);
const server = new XState<ServerState>(
initialServerList || {
serverList: [],
},
export const useServer = create(
() =>
initialServerList || {
serverList: [],
},
);

server.onChange((ss) => {
storage.setItem(Key.SERVER, ss);
});
useServer.subscribe((server) =>
storage
.setItem(Key.SERVER, server)
.catch((error) => logger.error(error, 'Failed to store server')),
);

window.setInterval(() => {
const selectedServer = getSelectedServer(server.get());
const selectedServer = getSelectedServer(useServer.getState());
if (selectedServer) {
import('@/server/base/get_metadata')
.then(({ default: getMetadata }) => getMetadata(selectedServer.origin))
.then((data) => {
if (
!definition.DEVELOPMENT &&
!data.version.startsWith(BETA_VERSION_START) &&
!data.version.startsWith('2.')
) {
/**
* @todo 不兼容提示
* @author mebtte<[email protected]>
*/
}

server.set((ss) => ({
...ss,
serverList: ss.serverList.map((s) =>
useServer.setState((server) => ({
serverList: server.serverList.map((s) =>
s.origin === selectedServer.origin
? {
...s,
Expand Down Expand Up @@ -77,31 +67,30 @@ window.setInterval(() => {

export function prefixServerOrigin(path: string) {
if (path) {
return `${getSelectedServer(server.get())?.origin}${path}`;
return `${getSelectedServer(useServer.getState())?.origin}${path}`;
}
return path;
}

export function useServer() {
const serverState = server.useState();
return getSelectedServer(serverState);
export function useSelectedServer() {
const server = useServer();
return getSelectedServer(server);
}

export function useUser() {
const selectedServer = useServer();
const selectedServer = useSelectedServer();
return selectedServer ? getSelectedUser(selectedServer) : undefined;
}

export async function reloadUser() {
const selectedServer = getSelectedServer(server.get());
const selectedServer = getSelectedServer(useServer.getState());
if (selectedServer) {
const user = getSelectedUser(selectedServer);
if (user) {
const { default: getProfile } = await import('@/server/api/get_profile');
const profile = await getProfile(user.token);
server.set((ss) => ({
...ss,
serverList: ss.serverList.map((s) =>
useServer.setState((server) => ({
serverList: server.serverList.map((s) =>
s.origin === selectedServer.origin
? {
...s,
Expand Down Expand Up @@ -133,5 +122,3 @@ export async function reloadUser() {
}
}
}

export default server;
21 changes: 9 additions & 12 deletions apps/pwa/src/global_states/setting.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import XState from '@/utils/x_state';
import { create } from 'zustand';
import storage, { Key } from '@/storage';
import { Setting } from '@/constants/setting';
import logger from '@/utils/logger';
Expand Down Expand Up @@ -26,26 +26,23 @@ const DEFAULT_SETTING: Setting = {
language: getInitialLanguage(),
};
const initialSetting = await storage.getItem(Key.SETTING);
const setting = new XState<Setting>({
export const useSetting = create<Setting>(() => ({
...DEFAULT_SETTING,
...initialSetting,
});
}));

/**
* correct language
* @author mebtte<[email protected]>
*/
if (!LANGUAGES.includes(setting.get().language)) {
setting.set((s) => ({
...s,
if (!LANGUAGES.includes(useSetting.getState().language)) {
useSetting.setState({
language: DEFAULT_LANGUAGE,
}));
});
}

setting.onChange((s) =>
useSetting.subscribe((setting) =>
storage
.setItem(Key.SETTING, s)
.catch((error) => logger.error(error, 'Failed to save setting')),
.setItem(Key.SETTING, setting)
.catch((error) => logger.error(error, 'Failed to store setting')),
);

export default setting;
20 changes: 8 additions & 12 deletions apps/pwa/src/global_states/theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,26 @@
import { MINI_MODE_MAX_WIDTH } from '@/constants';
import XState from '@/utils/x_state';
import throttle from 'lodash/throttle';
import scrollbarObserver from './scrollbar_observer';
import type { DefaultTheme } from 'styled-components';
import { create } from 'zustand';
import { Theme } from '@/styled';

const theme = new XState<DefaultTheme>({
export const useTheme = create<Theme>(() => ({
miniMode: window.innerWidth <= MINI_MODE_MAX_WIDTH,
autoScrollbar: scrollbarObserver.getScrollbarWidth() === 0,
});
}));

window.addEventListener(
'resize',
throttle(
() =>
theme.set((t) => ({
...t,
useTheme.setState({
miniMode: window.innerWidth <= MINI_MODE_MAX_WIDTH,
})),
}),
300,
),
);
scrollbarObserver.onChange(() =>
theme.set((t) => ({
...t,
useTheme.setState({
autoScrollbar: scrollbarObserver.getScrollbarWidth() === 0,
})),
}),
);

export default theme;
12 changes: 7 additions & 5 deletions apps/pwa/src/global_states/theme/scrollbar_observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import Eventin from 'eventin';
enum EventType {
RESIZE = 'resize',
}
type EventTypeMapData = {
interface EventTypeMapData {
[EventType.RESIZE]: null;
};
}

export default new (class {
class ScollbarObserver {
outer: HTMLDivElement;

inner: HTMLDivElement;

private eventemitter: Eventin<EventType, EventTypeMapData>;
private readonly eventemitter: Eventin<EventType, EventTypeMapData>;

constructor() {
this.eventemitter = new Eventin<EventType, EventTypeMapData>();
Expand Down Expand Up @@ -42,4 +42,6 @@ export default new (class {
onChange(listener: () => void) {
return this.eventemitter.listen(EventType.RESIZE, listener);
}
})();
}

export default new ScollbarObserver();
6 changes: 3 additions & 3 deletions apps/pwa/src/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Language } from '#/constants';
import setting from '../global_states/setting';
import { useSetting } from '@/global_states/setting';
import type { Key } from './constants';

let translation: { [key in Key]: string };
switch (setting.get().language) {
switch (useSetting.getState().language) {
case Language.ZH_HANS: {
({ default: translation } = await import('./zh_hans'));
break;
Expand Down Expand Up @@ -40,4 +40,4 @@ export const LANGUAGE_MAP: Record<
[Language.JA]: { label: '日本語' },
};

export { Key };
export type { Key };
Loading
Loading