Browse Source

useUndo

redux
YuJian920 3 years ago
parent
commit
2875ff470c
  1. 20
      src/components/ProjectModal.tsx
  2. 38
      src/components/ProjectPopover.tsx
  3. 46
      src/hook/useAsync.ts
  4. 16
      src/hook/useMountedRef.ts
  5. 7
      src/hook/useRequest.ts
  6. 88
      src/hook/useUndo.ts
  7. 54
      src/pages/Home/index.tsx
  8. 21
      src/pages/ProjectList/List/index.tsx
  9. 16
      src/pages/ProjectList/index.tsx

20
src/components/ProjectModal.tsx

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
import React from "react";
import { Button, Drawer } from "antd";
const ProjectModal = (props: {
projectModalOpen: boolean;
onClose: () => void;
}) => {
return (
<Drawer
onClose={props.onClose}
visible={props.projectModalOpen}
width="100%"
>
<h1>Project Modal</h1>
<Button onClick={props.onClose}></Button>
</Drawer>
);
};
export default React.memo(ProjectModal);

38
src/components/ProjectPopover.tsx

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
import React from "react";
import { Button, Divider, List, Popover, Typography } from "antd";
import { useProjects } from "../hook/useProjects";
import styled from "@emotion/styled";
const ProjectPopover = () => {
const { data: projects, isLoading } = useProjects();
const pinnedProjects = projects?.filter((projects) => projects.pin);
const content = (
<ContentContainer>
<Typography.Text type="secondary"></Typography.Text>
<List>
{pinnedProjects?.map((project) => (
<List.Item key={project.id}>
<List.Item.Meta title={project.name} />
</List.Item>
))}
</List>
<Divider />
<Button style={{ padding: 0 }} type="link">
</Button>
</ContentContainer>
);
return (
<Popover placement="bottom" content={content}>
<span></span>
</Popover>
);
};
const ContentContainer = styled.div`
min-width: 30rem;
`;
export default React.memo(ProjectPopover);

46
src/hook/useAsync.ts

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
import { useState } from "react";
import { useCallback, useState } from "react";
import useMountedRef from "./useMountedRef";
interface State<D> {
error: Error | null;
@ -25,26 +26,33 @@ const useAsync = <D>( @@ -25,26 +26,33 @@ const useAsync = <D>(
...defaultInitialState,
...initialState,
});
const mountedRef = useMountedRef();
const setData = (data: D) => setState({ data, stat: "success", error: null });
const setError = (error: Error) => {
const setData = useCallback(
(data: D) => setState({ data, stat: "success", error: null }),
[]
);
const setError = useCallback((error: Error) => {
setState({ data: null, stat: "error", error });
}
const run = (promise: Promise<D>) => {
if (!promise || !promise.then) throw new Error("请传入 Promise 数据类型");
setState({ ...state, stat: "loading" });
return promise
.then((data) => {
setData(data);
return data;
})
.catch((error) => {
setError(error);
if (config.throwOnError) return Promise.reject(error);
return error;
});
};
}, []);
const run = useCallback(
(promise: Promise<D>) => {
if (!promise || !promise.then) throw new Error("请传入 Promise 数据类型");
setState((prevState) => ({ ...prevState, stat: "loading" }));
return promise
.then((data) => {
if (mountedRef.current) setData(data);
return data;
})
.catch((error) => {
setError(error);
if (config.throwOnError) return Promise.reject(error);
return error;
});
},
[config.throwOnError, mountedRef, setData, setData, setError]
);
return {
isIdle: state.stat === "idle",

16
src/hook/useMountedRef.ts

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
import { useEffect, useRef } from "react";
const useMountedRef = () => {
const mountedRef = useRef(false);
useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
};
}, []);
return mountedRef;
};
export default useMountedRef;

7
src/hook/useRequest.ts

@ -1,9 +1,14 @@ @@ -1,9 +1,14 @@
import { useCallback } from "react";
import { useAuth } from "../context/auth-context";
import { request } from "../utils/request";
const useRequest = () => {
const { user } = useAuth();
return (...[endpoint, config]: Parameters<typeof request>) => request(endpoint, { ...config, token: user?.token });
return useCallback(
(...[endpoint, config]: Parameters<typeof request>) =>
request(endpoint, { ...config, token: user?.token }),
[user?.token]
);
};
export default useRequest;

88
src/hook/useUndo.ts

@ -0,0 +1,88 @@ @@ -0,0 +1,88 @@
import { useCallback, useReducer, useState } from "react";
type State<T> = {
past: T[];
present: T;
future: T[];
};
type Action<T> = {
newPresent?: T;
type: typeof UNDO | typeof REDO | typeof SET | typeof RESET;
};
const UNDO = "UNDO";
const REDO = "REDO";
const SET = "SET";
const RESET = "RESET";
const undoReducer = <T>(state: State<T>, action: Action<T>) => {
const { past, present, future } = state;
const { newPresent, type } = action;
switch (type) {
case UNDO: {
if (past.length === 0) return state;
const previous = past[past.length - 1];
const newPast = past.slice(0, past.length - 1);
return {
past: newPast,
present: previous,
future: [present, ...future],
};
}
case REDO: {
if (future.length !== 0) return state;
const previous = future[0];
const newPast = future.slice(1);
return {
past: [...past, previous],
present: previous,
future: newPast,
};
}
case SET: {
if (newPresent === present) return state;
return {
past: [...past, newPresent],
present: newPresent,
future: [],
};
}
case RESET: {
return {
past: [],
present: newPresent,
future: [],
};
}
}
};
const useUndo = <T>(initialPresent: T) => {
const [state, dispatch] = useReducer(undoReducer, {
past: [],
present: initialPresent,
future: [],
} as State<T>);
const canUndo = state.past.length !== 0;
const canRedo = state.future.length !== 0;
const undo = useCallback(() => dispatch({ type: UNDO }), []);
const redo = useCallback(() => dispatch({ type: REDO }), []);
const set = useCallback((newPresent: T) => dispatch({ type: SET, newPresent }), []);
const reset = useCallback((newPresent: T) => dispatch({ type: RESET, newPresent }), []);
return [state, { set, reset, undo, redo, canUndo, canRedo }] as const;
};
export default useUndo;

54
src/pages/Home/index.tsx

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
import React from "react";
import React, { useState } from "react";
import { Dropdown, Menu, Button } from "antd";
import { Navigate, Route, Routes } from "react-router";
import { BrowserRouter } from "react-router-dom";
@ -8,8 +8,11 @@ import { useAuth } from "../../context/auth-context"; @@ -8,8 +8,11 @@ import { useAuth } from "../../context/auth-context";
import { ReactComponent as SoftwareLogo } from "../../assets/software-logo.svg";
import { Main, Container, Header, HeaderLeft, HeaderRight } from "./style";
import { resetRoute } from "../../utils";
import ProjectModal from "../../components/ProjectModal";
import ProjectPopover from "../../components/ProjectPopover";
const Home = () => {
const [projectModalOpen, setProjectModalOpen] = useState(false);
return (
<Container>
<PageHeader />
@ -22,40 +25,51 @@ const Home = () => { @@ -22,40 +25,51 @@ const Home = () => {
</Routes>
</BrowserRouter>
</Main>
<ProjectModal
projectModalOpen={projectModalOpen}
onClose={() => setProjectModalOpen(false)}
/>
</Container>
);
};
const PageHeader = () => {
const { logout, user } = useAuth();
return (
<Header between={true}>
<HeaderLeft gap={true}>
<Button type="link" onClick={resetRoute}>
<Button style={{ padding: 0 }} type="link" onClick={resetRoute}>
<SoftwareLogo width="18rem" color="rgb(38, 132, 255)" />
</Button>
<h2></h2>
<h2></h2>
<ProjectPopover />
<span></span>
</HeaderLeft>
<HeaderRight>
<Dropdown
overlay={
<Menu>
<Menu.Item key="logout">
<Button type="link" onClick={logout}>
</Button>
</Menu.Item>
</Menu>
}
>
<Button type="link" onClick={(e) => e.preventDefault()}>
Hi, {user?.name}
</Button>
</Dropdown>
<User />
</HeaderRight>
</Header>
);
};
const User = () => {
const { logout, user } = useAuth();
return (
<Dropdown
overlay={
<Menu>
<Menu.Item key="logout">
<Button type="link" onClick={logout}>
</Button>
</Menu.Item>
</Menu>
}
>
<Button type="link" onClick={(e) => e.preventDefault()}>
Hi, {user?.name}
</Button>
</Dropdown>
);
};
export default Home;

21
src/pages/ProjectList/List/index.tsx

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
import React from "react";
import { Link } from "react-router-dom";
import { Table, TableProps } from "antd";
import { Button, Dropdown, Menu, Table, TableProps } from "antd";
import dayjs from "dayjs";
import { Project, User } from "../../../type";
import Pin from "../../../components/Pin";
@ -61,6 +61,25 @@ const ProjectList = ({ users, ...props }: ListProps) => { @@ -61,6 +61,25 @@ const ProjectList = ({ users, ...props }: ListProps) => {
);
},
},
{
render: (value, project) => {
return (
<Dropdown
overlay={
<Menu>
<Menu.Item key="edit">
<Button type="link" style={{ padding: 0 }}></Button>
</Menu.Item>
</Menu>
}
>
<Button type="link" style={{ padding: 0 }}>
...
</Button>
</Dropdown>
);
},
},
]}
rowKey="id"
{...props}

16
src/pages/ProjectList/index.tsx

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import React, { useState } from "react";
import { Typography } from "antd";
import { Button, Typography } from "antd";
import useDebounce from "../../hook/useDebounce";
import List from "./List";
import SearchPanel from "./Search";
@ -8,17 +8,25 @@ import { useProjects } from "../../hook/useProjects"; @@ -8,17 +8,25 @@ import { useProjects } from "../../hook/useProjects";
import useUsers from "../../hook/useUsers";
import useDocumentTitle from "../../hook/useDocumentTitle";
import useProjectsSearchParams from "../../hook/useProjectsSearchParams";
import { Row } from "../../components/lib";
const ProjectList = () => {
useDocumentTitle("项目列表", false);
const [params, setParam] = useProjectsSearchParams()
const { isLoading, error, data: list } = useProjects(useDebounce(params, 200));
const [params, setParam] = useProjectsSearchParams();
const {
isLoading,
error,
data: list,
} = useProjects(useDebounce(params, 200));
const { data: users } = useUsers();
return (
<Container>
<h1></h1>
<Row between={true}>
<h1></h1>
<Button></Button>
</Row>
<SearchPanel users={users || []} param={params} setParam={setParam} />
{error ? (
<Typography.Text type="danger">{error.message}</Typography.Text>

Loading…
Cancel
Save