build(deps):增加一些基本的项目配置和基础代码结构。
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
.logo.vite:hover {
|
||||
filter: drop-shadow(0 0 2em #747bff);
|
||||
}
|
||||
|
||||
.logo.react:hover {
|
||||
filter: drop-shadow(0 0 2em #61dafb);
|
||||
}
|
15
src/MainLayout.tsx
Normal file
15
src/MainLayout.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Box, Group } from "@mantine/core";
|
||||
import { FC } from "react";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import { NavMenu } from "./NavMenu";
|
||||
|
||||
export const MainLayout: FC = () => {
|
||||
return (
|
||||
<Group grow noWrap spacing={0} h="100%" w="100%">
|
||||
<NavMenu />
|
||||
<Box h="inherit" w="inherit" maw="100%" sx={{ flexGrow: 5 }}>
|
||||
<Outlet />
|
||||
</Box>
|
||||
</Group>
|
||||
);
|
||||
};
|
32
src/NavMenu.tsx
Normal file
32
src/NavMenu.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Stack, useMantineTheme } from "@mantine/core";
|
||||
import { ifElse, path, propEq } from "ramda";
|
||||
import { FC, useMemo } from "react";
|
||||
|
||||
const bgSelectFn = ifElse(
|
||||
propEq("colorScheme", "dark"),
|
||||
path(["colors", "cbg", 2]),
|
||||
path(["colors", "cbg", 7])
|
||||
);
|
||||
|
||||
export const NavMenu: FC = () => {
|
||||
const theme = useMantineTheme();
|
||||
const normalColor = useMemo(() => path(["violet", 7])(theme.colors), [theme.colors]);
|
||||
const activatedColor = useMemo(() => path<string>(["violet", 3])(theme.colors), [theme.colors]);
|
||||
const disabledColor = useMemo(() => path<string>(["gray", 7])(theme.colors), [theme.colors]);
|
||||
const navMenuBg = useMemo(() => bgSelectFn(theme), [theme, theme]);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
spacing={24}
|
||||
maw={64}
|
||||
h="inherit"
|
||||
sx={(theme) => ({
|
||||
flexGrow: 1,
|
||||
backgroundColor: navMenuBg,
|
||||
})}
|
||||
align="center"
|
||||
px={16}
|
||||
py={16}
|
||||
></Stack>
|
||||
);
|
||||
};
|
28
src/main.tsx
28
src/main.tsx
@@ -1,10 +1,30 @@
|
||||
import React from "react";
|
||||
import { NotificationsProvider } from "@mantine/notifications";
|
||||
import React, { FC, useState } from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "./styles.css";
|
||||
import { RouterProvider } from "react-router-dom";
|
||||
import { appRouter } from "./router";
|
||||
import { useAppTheme } from "./theme";
|
||||
|
||||
const AppMain: FC = () => {
|
||||
const preferredColorScheme = useColorScheme();
|
||||
const [colorScheme, setColorScheme] = useState<ColorScheme>(preferredColorScheme);
|
||||
const theme = useAppTheme();
|
||||
|
||||
return (
|
||||
<React.StrictMode>
|
||||
<ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={setColorScheme}>
|
||||
<MantineProvider theme={theme} withGlobalStyles withNormalizeCSS>
|
||||
<NotificationsProvider position="bottom-right" limit={5} zIndex={999}>
|
||||
<RouterProvider router={appRouter} />
|
||||
</NotificationsProvider>
|
||||
</MantineProvider>
|
||||
</ColorSchemeProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
<AppMain />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
9
src/router.tsx
Normal file
9
src/router.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createBrowserRouter } from "react-router-dom";
|
||||
import { MainLayout } from "./MainLayout";
|
||||
|
||||
export const appRouter = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element: <MainLayout />,
|
||||
},
|
||||
]);
|
109
src/styles.css
109
src/styles.css
@@ -1,109 +0,0 @@
|
||||
:root {
|
||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 400;
|
||||
|
||||
color: #0f0f0f;
|
||||
background-color: #f6f6f6;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0;
|
||||
padding-top: 10vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: 0.75s;
|
||||
}
|
||||
|
||||
.logo.tauri:hover {
|
||||
filter: drop-shadow(0 0 2em #24c8db);
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
color: #0f0f0f;
|
||||
background-color: #ffffff;
|
||||
transition: border-color 0.25s;
|
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
border-color: #396cd8;
|
||||
}
|
||||
button:active {
|
||||
border-color: #396cd8;
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#greet-input {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
color: #f6f6f6;
|
||||
background-color: #2f2f2f;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #24c8db;
|
||||
}
|
||||
|
||||
input,
|
||||
button {
|
||||
color: #ffffff;
|
||||
background-color: #0f0f0f98;
|
||||
}
|
||||
button:active {
|
||||
background-color: #0f0f0f69;
|
||||
}
|
||||
}
|
61
src/theme.ts
Normal file
61
src/theme.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { MantineTheme } from '@mantine/core';
|
||||
import { useColorScheme } from '@mantine/hooks';
|
||||
import { ifElse, path, propEq } from 'ramda';
|
||||
|
||||
const bgColorSelectFn = ifElse(
|
||||
propEq('colorScheme', 'light'),
|
||||
path(['colors', 'cbg', 8]),
|
||||
path(['colors', 'cbg', 0])
|
||||
);
|
||||
const fgColorSelectFn = ifElse(
|
||||
propEq('colorScheme', 'light'),
|
||||
path(['colors', 'cfg', 0]),
|
||||
path(['colors', 'cfg', 8])
|
||||
);
|
||||
|
||||
export function useAppTheme(): Partial<MantineTheme> {
|
||||
const colorScheme = useColorScheme();
|
||||
|
||||
return {
|
||||
colorScheme,
|
||||
focusRing: 'never',
|
||||
defaultRadius: 'xs',
|
||||
colors: {
|
||||
cfg: [
|
||||
'#0f0f0f',
|
||||
'#151515',
|
||||
'#262626',
|
||||
'#414141',
|
||||
'#626262',
|
||||
'#878787',
|
||||
'#acacac',
|
||||
'#cecece',
|
||||
'#e8e8e8',
|
||||
'#f9f9f9'
|
||||
],
|
||||
cbg: [
|
||||
'#1a202c',
|
||||
'#1e2533',
|
||||
'#2a3446',
|
||||
'#3c4a65',
|
||||
'#53678b',
|
||||
'#7689ad',
|
||||
'#a0adc6',
|
||||
'#c6cedd',
|
||||
'#e5e8ef',
|
||||
'#f8f9fb'
|
||||
]
|
||||
},
|
||||
primaryColor: 'violet',
|
||||
globalStyles: theme => ({
|
||||
'html, body': {
|
||||
height: '100vh',
|
||||
color: fgColorSelectFn(theme),
|
||||
backgroundColor: bgColorSelectFn(theme)
|
||||
},
|
||||
'#root': {
|
||||
height: '100%'
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
216
src/types.ts
Normal file
216
src/types.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
/**
|
||||
* 不需要任何参数即可以生成指定类型对象的工厂函数类型。
|
||||
*/
|
||||
export type Factory<T> = () => T;
|
||||
|
||||
/**
|
||||
* 接受一个参数来生成一个指定类型的新对象的工厂函数类型。
|
||||
*/
|
||||
export type MapFactory<T, P = unknown> = (obj: P) => T;
|
||||
|
||||
/**
|
||||
* 可能接受一个参数也可以不使用任何参数来生成指定类型新对象的工厂函数类型。
|
||||
*/
|
||||
export type MaybeParamFactory<T, P = unknown> = (obj?: P) => T;
|
||||
|
||||
/**
|
||||
* 可能接受一组任意类型的参数集合来生成指定类型新对象的工厂函数类型。
|
||||
*/
|
||||
export type ParamFactory<T, P = unknown> = (...args: P[]) => T;
|
||||
|
||||
/**
|
||||
* 接受一个对象(默认是字符串)作为参数的回调函数,可以是同步函数也可以是异步函数。
|
||||
*/
|
||||
export type ObjectCallback<P = string, T = void> = (obj: P) => T | Promise<T>;
|
||||
|
||||
/**
|
||||
* 接受一个对象(默认是字符串)作为参数的回调函数,仅可以是同步函数。
|
||||
*/
|
||||
export type SyncObjectCallback<P = string, T = void> = (obj: P) => T;
|
||||
|
||||
/**
|
||||
* 接受一个对象(默认是字符串)作为参数的回调函数,仅可以是异步函数。
|
||||
*/
|
||||
export type AsyncObjectCallback<P = string, T = void> = (obj: P) => Promise<T>;
|
||||
|
||||
/**
|
||||
* 接受一个对象(默认是字符串)或者零个对象作为参数的回调函数,可以是同步函数也可以是异步函数。
|
||||
*/
|
||||
export type MaybeObjectCallback<P = string, T = void> = ObjectCallback<P | undefined, T>;
|
||||
|
||||
/**
|
||||
* 接受一个对象(默认是字符串)或者零个对象作为参数的回调函数,仅可以是同步函数。
|
||||
*/
|
||||
export type SyncMaybeObjectCallback<P = string, T = void> = SyncObjectCallback<P | undefined, T>;
|
||||
|
||||
/**
|
||||
* 接受一个对象(默认是字符串)或者零个对象作为参数的回调函数,仅可以是异步函数。
|
||||
*/
|
||||
export type AsyncMaybeObjectCallback<P = string, T = void> = AsyncObjectCallback<P | undefined, T>;
|
||||
|
||||
/**
|
||||
* 接受一个对象数组(默认是字符串数组)作为参数的回调函数,可以是同步函数也可以是异步函数。
|
||||
*/
|
||||
export type ObjectsCallback<P = string, T = void> = ObjectCallback<P[], T>;
|
||||
|
||||
/**
|
||||
* 接受一个对象数组(默认是字符串数组)作为参数的回调函数,仅可以是同步函数。
|
||||
*/
|
||||
export type SyncObjectsCallback<P = string, T = void> = SyncObjectCallback<P[], T>;
|
||||
|
||||
/**
|
||||
* 接受一个对象数组(默认是字符串数组)作为参数的回调函数,仅可以是异步函数。
|
||||
*/
|
||||
export type AsyncObjectsCallback<P = string, T = void> = AsyncObjectCallback<P[], T>;
|
||||
|
||||
/**
|
||||
* 接收一个对象数组(默认是字符串数组)或者零个对象作为参数的回调函数,可以是同步函数也可以是异步函数。
|
||||
*/
|
||||
export type MaybeObjectsCallback<P = string, T = void> = ObjectCallback<P[] | undefined, T>;
|
||||
|
||||
/**
|
||||
* 接收一个对象数组(默认是字符串数组)或者零个对象作为参数的回调函数,仅可以是同步函数。
|
||||
*/
|
||||
export type SyncMaybeObjectsCallback<P = string, T = void> = SyncObjectCallback<P[] | undefined, T>;
|
||||
|
||||
/**
|
||||
* 接收一个对象数组(默认是字符串数组)或者零个对象作为参数的回调函数,仅可以是异步函数。
|
||||
*/
|
||||
export type AsyncMaybeObjectsCallback<P = string, T = void> = AsyncObjectCallback<
|
||||
P[] | undefined,
|
||||
T
|
||||
>;
|
||||
|
||||
/**
|
||||
* 可以接受任意数量任意参数类型的回调函数。
|
||||
*/
|
||||
export type ExtendParamCallback<P = unknown, T = void> = (...args: P[]) => T | Promise<T>;
|
||||
|
||||
/**
|
||||
* 可以接受任意数量任意参数类型的仅支持同步的回调函数。
|
||||
*/
|
||||
export type SyncExtendParamCallback<P = unknown, T = void> = (...args: P[]) => T;
|
||||
|
||||
/**
|
||||
* 可以接受任意数量任意参数类型的仅支持异步的回调函数。
|
||||
*/
|
||||
export type AsyncExtendParamCallback<P = unknown, T = void> = (...args: P[]) => Promise<T>;
|
||||
|
||||
/**
|
||||
* 不接受任何参数内容的回调函数,可以是同步函数也可以是异步函数。
|
||||
*/
|
||||
export type Callback<T = void> = () => T | Promise<T>;
|
||||
|
||||
/**
|
||||
* 不接受任何参数内容的回调函数,仅可以是同步函数。
|
||||
*/
|
||||
export type SyncCallback<T = void> = () => T;
|
||||
|
||||
/**
|
||||
* 不接受任何参数内容的回调函数,仅可以是异步函数。
|
||||
*/
|
||||
export type AsyncCallback<T = void> = () => Promise<T>;
|
||||
|
||||
/**
|
||||
* 用于在Store中定义状态操作Action的无参同步Action类型。
|
||||
*/
|
||||
export type SyncAction = SyncCallback<void>;
|
||||
|
||||
/**
|
||||
* 用于在Store中定义状态操作Action的无参可异步Action类型。
|
||||
*/
|
||||
export type AsyncAction = Callback<void>;
|
||||
|
||||
/**
|
||||
* 用于在Store中定义状态操作Action的单一参数同步Action类型。
|
||||
*/
|
||||
export type SyncParamAction<T> = SyncObjectCallback<T, void>;
|
||||
|
||||
/**
|
||||
* 用于在Store中定义状态操作Action的单一参数异步Action类型。
|
||||
*/
|
||||
export type AsyncParamAction<T> = AsyncObjectCallback<T, void>;
|
||||
|
||||
/**
|
||||
* 用于在Store中定义状态操作Action的不定参同步Action类型。
|
||||
*/
|
||||
export type SyncMaybeAction<T> = SyncMaybeObjectCallback<T>;
|
||||
|
||||
/**
|
||||
* 用于在Store中定义状态操作Action的不定参可异步Action类型。
|
||||
*/
|
||||
export type AsyncMaybeAction<T> = AsyncMaybeObjectCallback<T>;
|
||||
|
||||
/**
|
||||
* 用于在Store中定义状态操作Action的展开参同步Action类型。
|
||||
*/
|
||||
export type SyncExtendParamAction<T> = SyncExtendParamCallback<T>;
|
||||
|
||||
/**
|
||||
* 用于在Store中定义状态操作Action的展开参可异步Action类型。
|
||||
*/
|
||||
export type AsyncExtendParamAction<T> = AsyncExtendParamCallback<T>;
|
||||
|
||||
/**
|
||||
* 用于定义可以接受重置事件的Ref组件。
|
||||
*/
|
||||
export interface Resetable {
|
||||
/**
|
||||
* 组件可以接受的重置方法。
|
||||
*/
|
||||
reset: Callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于定义可执行打开动作的Ref组件。
|
||||
*/
|
||||
export interface Openable {
|
||||
/**
|
||||
* 组件可以接受的打开方法。
|
||||
*/
|
||||
open: Callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于定义可执行关闭动作的Ref组件。
|
||||
*/
|
||||
export interface Closeable {
|
||||
/**
|
||||
* 组件可以接受的关闭方法。
|
||||
*/
|
||||
close: Callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于定义可以执行特定动作的Ref组件。
|
||||
* 组件所执行的动作可以接受任意数量的参数,但不会返回任何结果。
|
||||
*/
|
||||
export interface Actionable<T = unknown> {
|
||||
/**
|
||||
* 组件可以接受的执行特定动作的方法。
|
||||
*/
|
||||
action?: ExtendParamCallback<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于记录可以发生修改的脏数据。
|
||||
*/
|
||||
export interface DirtyableValue<T> {
|
||||
/**
|
||||
* 记录数据当前的值。
|
||||
*/
|
||||
value: T;
|
||||
/**
|
||||
* 记录数据在修改前的全部历史值。
|
||||
*/
|
||||
lastValues: T[];
|
||||
/**
|
||||
* 记录数据是否发生了更改。
|
||||
*/
|
||||
dirty: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换一般的数据记录类型成为可记录修改的脏数据类型。
|
||||
*/
|
||||
export type Dirtyable<T> = { [P in keyof T]: DirtyableValue<T[P]> };
|
56
src/utils/store_creator.ts
Normal file
56
src/utils/store_creator.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
//@ts-nocheck
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import { create, State, StateCreator, StoreApi, UseBoundStore } from 'zustand';
|
||||
import { immer } from 'zustand/middleware/immer';
|
||||
|
||||
interface EnhancedStoreType<StoreType> {
|
||||
use: {
|
||||
[key in keyof StoreType]: () => StoreType[key];
|
||||
};
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
type CreateStoreHookOptions = {
|
||||
debug?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* 自动为一个Store Hook创建快速访问其中状态和Action的选择器。
|
||||
*/
|
||||
function createSelectors<StoreType extends State>(
|
||||
store: UseBoundStore<StoreApi<StoreType>>,
|
||||
debug?: boolean
|
||||
): UseBoundStore<StoreApi<StoreType>> & EnhancedStoreType<StoreType> {
|
||||
const initialState = store.getState();
|
||||
(store as unknown).use = {};
|
||||
|
||||
Object.keys(store.getState()).forEach(key => {
|
||||
const selector = (state: StoreType) => state[key as keyof StoreType];
|
||||
(store as unknown).use[key] = () => store(selector);
|
||||
});
|
||||
(store as unknown).reset = () => store.setState(initialState, true);
|
||||
|
||||
if (debug ?? false) {
|
||||
store.subscribe((current, previous) => {
|
||||
console.log('[状态调试]Action应用前: ', previous);
|
||||
console.log('[状态调试]Action应用后: ', current);
|
||||
});
|
||||
}
|
||||
|
||||
return store as UseBoundStore<StoreType> & EnhancedStoreType<StoreType>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动嵌套使用Devtools和Immer中间件的Zustand创建Store Hook的函数。
|
||||
* 同时将会自动应用创建快速访问状态和Action的选择器。
|
||||
*/
|
||||
export const createStoreHook = <
|
||||
T extends State,
|
||||
Mps extends [StoreMutatorIdentifier, unknown][] = [],
|
||||
Mcs extends [StoreMutatorIdentifier, unknown][] = []
|
||||
>(
|
||||
initializer: StateCreator<T, [...Mps, ['zustand/immer', never]], Mcs>,
|
||||
options?: CreateStoreHookOptions
|
||||
): UseBoundStore<StoreApi<T>> & EnhancedStoreType<T> =>
|
||||
createSelectors(create<T>()(immer(initializer)), options?.debug ?? false);
|
Reference in New Issue
Block a user