mirror of
https://github.com/aykhans/slash-e.git
synced 2025-04-18 21:19:44 +00:00
feat: add view order setting
This commit is contained in:
parent
15ffd0738c
commit
e7cf0c2f79
48
web/src/components/OrderSetting.tsx
Normal file
48
web/src/components/OrderSetting.tsx
Normal 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;
|
@ -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]);
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user