feat: add view order setting

This commit is contained in:
Steven 2023-07-17 22:43:15 +08:00
parent 15ffd0738c
commit e7cf0c2f79
4 changed files with 130 additions and 32 deletions

View File

@ -0,0 +1,48 @@
import { Select, Option, Button } from "@mui/joy";
import useViewStore from "../stores/v1/view";
import Dropdown from "./common/Dropdown";
import Icon from "./Icon";
const OrderSetting = () => {
const viewStore = useViewStore();
const order = viewStore.getOrder();
const { field, direction } = order;
return (
<Dropdown
trigger={
<button className="p-1 mr-2">
<Icon.ListFilter className="w-5 h-auto text-gray-600" />
</button>
}
actions={
<div className="w-52 p-2 pt-0 gap-2 flex flex-col justify-start items-start" onClick={(e) => e.stopPropagation()}>
<div className="w-full flex flex-row justify-between items-center mt-1">
<span className="text-sm font-medium">View order</span>
<Button size="sm" variant="plain" color="neutral" onClick={() => viewStore.setOrder({ field: "name", direction: "asc" })}>
<Icon.RefreshCw className="w-4 h-auto" />
</Button>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span className="text-sm shrink-0 mr-2">Order by</span>
<Select size="sm" value={field} onChange={(_, value) => viewStore.setOrder({ field: value as any })}>
<Option value={"name"}>Name</Option>
<Option value={"updatedTs"}>CreatedAt</Option>
<Option value={"createdTs"}>UpdatedAt</Option>
<Option value={"view"}>Visits</Option>
</Select>
</div>
<div className="w-full flex flex-row justify-between items-center">
<span className="text-sm shrink-0 mr-2">Direction</span>
<Select size="sm" value={direction} onChange={(_, value) => viewStore.setOrder({ direction: value as any })}>
<Option value={"asc"}>ASC</Option>
<Option value={"desc"}>DESC</Option>
</Select>
</div>
</div>
}
></Dropdown>
);
};
export default OrderSetting;

View File

@ -21,10 +21,15 @@ const Dropdown: React.FC<Props> = (props: Props) => {
toggleDropdownStatus(false); toggleDropdownStatus(false);
} }
}; };
window.addEventListener("click", handleClickOutside, { window.addEventListener("click", handleClickOutside, {
capture: true, capture: true,
once: true,
}); });
return () => {
window.removeEventListener("click", handleClickOutside, {
capture: true,
});
};
} }
}, [dropdownStatus]); }, [dropdownStatus]);

View File

@ -2,41 +2,19 @@ import { Button, Tab, TabList, Tabs } from "@mui/joy";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { shortcutService } from "../services"; import { shortcutService } from "../services";
import { useAppSelector } from "../stores"; import { useAppSelector } from "../stores";
import useViewStore, { Filter } from "../stores/v1/view"; import useViewStore, { getFilteredShortcutList, getOrderedShortcutList } from "../stores/v1/view";
import useUserStore from "../stores/v1/user"; import useUserStore from "../stores/v1/user";
import useLoading from "../hooks/useLoading"; import useLoading from "../hooks/useLoading";
import Icon from "../components/Icon"; import Icon from "../components/Icon";
import ShortcutListView from "../components/ShortcutListView"; import ShortcutListView from "../components/ShortcutListView";
import CreateShortcutDialog from "../components/CreateShortcutDialog"; import CreateShortcutDialog from "../components/CreateShortcutDialog";
import FilterView from "../components/FilterView"; import FilterView from "../components/FilterView";
import OrderSetting from "../components/OrderSetting";
interface State { interface State {
showCreateShortcutDialog: boolean; showCreateShortcutDialog: boolean;
} }
const getFilteredShortcutList = (shortcutList: Shortcut[], filter: Filter, currentUser: User) => {
const { tag, mineOnly, visibility } = filter;
const filteredShortcutList = shortcutList.filter((shortcut) => {
if (tag) {
if (!shortcut.tags.includes(tag)) {
return false;
}
}
if (mineOnly) {
if (shortcut.creatorId !== currentUser.id) {
return false;
}
}
if (visibility) {
if (shortcut.visibility !== visibility) {
return false;
}
}
return true;
});
return filteredShortcutList;
};
const Home: React.FC = () => { const Home: React.FC = () => {
const loadingState = useLoading(); const loadingState = useLoading();
const currentUser = useUserStore().getCurrentUser(); const currentUser = useUserStore().getCurrentUser();
@ -47,6 +25,7 @@ const Home: React.FC = () => {
}); });
const filter = viewStore.filter; const filter = viewStore.filter;
const filteredShortcutList = getFilteredShortcutList(shortcutList, filter, currentUser); const filteredShortcutList = getFilteredShortcutList(shortcutList, filter, currentUser);
const orderedShortcutList = getOrderedShortcutList(filteredShortcutList, viewStore.order);
useEffect(() => { useEffect(() => {
Promise.all([shortcutService.getMyAllShortcuts()]).finally(() => { Promise.all([shortcutService.getMyAllShortcuts()]).finally(() => {
@ -69,6 +48,12 @@ const Home: React.FC = () => {
</div> </div>
<div className="w-full flex flex-row justify-between items-center mb-4"> <div className="w-full flex flex-row justify-between items-center mb-4">
<div className="flex flex-row justify-start items-center"> <div className="flex flex-row justify-start items-center">
<Button className="shadow" variant="soft" size="sm" onClick={() => setShowCreateShortcutDialog(true)}>
<Icon.Plus className="w-5 h-auto" /> New
</Button>
</div>
<div className="flex flex-row justify-end items-center">
<OrderSetting />
<Tabs <Tabs
value={filter.mineOnly ? "PRIVATE" : "ALL"} value={filter.mineOnly ? "PRIVATE" : "ALL"}
size="sm" size="sm"
@ -80,11 +65,6 @@ const Home: React.FC = () => {
</TabList> </TabList>
</Tabs> </Tabs>
</div> </div>
<div>
<Button className="shadow" variant="soft" size="sm" onClick={() => setShowCreateShortcutDialog(true)}>
<Icon.Plus className="w-5 h-auto" /> New
</Button>
</div>
</div> </div>
<FilterView /> <FilterView />
@ -94,13 +74,13 @@ const Home: React.FC = () => {
<Icon.Loader className="mr-2 w-5 h-auto animate-spin" /> <Icon.Loader className="mr-2 w-5 h-auto animate-spin" />
loading loading
</div> </div>
) : filteredShortcutList.length === 0 ? ( ) : orderedShortcutList.length === 0 ? (
<div className="py-16 w-full flex flex-col justify-center items-center"> <div className="py-16 w-full flex flex-col justify-center items-center">
<Icon.PackageOpen className="w-16 h-auto text-gray-400" /> <Icon.PackageOpen className="w-16 h-auto text-gray-400" />
<p className="mt-4">No shortcuts found.</p> <p className="mt-4">No shortcuts found.</p>
</div> </div>
) : ( ) : (
<ShortcutListView shortcutList={filteredShortcutList} /> <ShortcutListView shortcutList={orderedShortcutList} />
)} )}
</div> </div>

View File

@ -7,18 +7,39 @@ export interface Filter {
visibility?: Visibility; visibility?: Visibility;
} }
export interface Order {
field: "name" | "createdTs" | "updatedTs" | "view";
direction: "asc" | "desc";
}
interface ViewState { interface ViewState {
filter: Filter; filter: Filter;
order: Order;
setFilter: (filter: Partial<Filter>) => void; setFilter: (filter: Partial<Filter>) => void;
getOrder: () => Order;
setOrder: (order: Partial<Order>) => void;
} }
const useViewStore = create<ViewState>()( const useViewStore = create<ViewState>()(
persist( persist(
(set, get) => ({ (set, get) => ({
filter: {}, filter: {},
order: {
field: "name",
direction: "asc",
},
setFilter: (filter: Partial<Filter>) => { setFilter: (filter: Partial<Filter>) => {
set({ filter: { ...get().filter, ...filter } }); set({ filter: { ...get().filter, ...filter } });
}, },
getOrder: () => {
return {
field: get().order.field || "name",
direction: get().order.direction || "asc",
};
},
setOrder: (order: Partial<Order>) => {
set({ order: { ...get().order, ...order } });
},
}), }),
{ {
name: "view", name: "view",
@ -26,4 +47,48 @@ const useViewStore = create<ViewState>()(
) )
); );
export const getFilteredShortcutList = (shortcutList: Shortcut[], filter: Filter, currentUser: User) => {
const { tag, mineOnly, visibility } = filter;
const filteredShortcutList = shortcutList.filter((shortcut) => {
if (tag) {
if (!shortcut.tags.includes(tag)) {
return false;
}
}
if (mineOnly) {
if (shortcut.creatorId !== currentUser.id) {
return false;
}
}
if (visibility) {
if (shortcut.visibility !== visibility) {
return false;
}
}
return true;
});
return filteredShortcutList;
};
export const getOrderedShortcutList = (shortcutList: Shortcut[], order: Order) => {
const { field, direction } = {
field: order.field || "name",
direction: order.direction || "asc",
};
const orderedShortcutList = shortcutList.sort((a, b) => {
if (field === "name") {
return direction === "asc" ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
} else if (field === "createdTs") {
return direction === "asc" ? a.createdTs - b.createdTs : b.createdTs - a.createdTs;
} else if (field === "updatedTs") {
return direction === "asc" ? a.updatedTs - b.updatedTs : b.updatedTs - a.updatedTs;
} else if (field === "view") {
return direction === "asc" ? a.view - b.view : b.view - a.view;
} else {
return 0;
}
});
return orderedShortcutList;
};
export default useViewStore; export default useViewStore;