mirror of
https://github.com/aykhans/slash-e.git
synced 2025-07-04 12:26:19 +00:00
fix: update shortcut
This commit is contained in:
@ -11,13 +11,12 @@
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@fontsource/inter": "^5.0.16",
|
||||
"@mui/joy": "5.0.0-beta.17",
|
||||
"@mui/joy": "5.0.0-beta.19",
|
||||
"@reduxjs/toolkit": "^1.9.7",
|
||||
"classnames": "^2.3.2",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"dayjs": "^1.11.10",
|
||||
"i18next": "^23.7.8",
|
||||
"i18next": "^23.7.11",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-react": "^0.292.0",
|
||||
"nice-grpc-web": "^3.3.2",
|
||||
@ -26,22 +25,22 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-i18next": "^13.5.0",
|
||||
"react-router-dom": "^6.20.1",
|
||||
"react-router-dom": "^6.21.1",
|
||||
"react-use": "^17.4.2",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@bufbuild/buf": "^1.28.1",
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/react": "^18.2.43",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
||||
"@typescript-eslint/parser": "^6.12.0",
|
||||
"@types/react": "^18.2.45",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
||||
"@typescript-eslint/parser": "^6.15.0",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^8.10.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
@ -50,6 +49,9 @@
|
||||
"prettier": "2.6.2",
|
||||
"protobufjs": "^7.2.5",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.0.7"
|
||||
"vite": "^5.0.10"
|
||||
},
|
||||
"resolutions": {
|
||||
"csstype": "3.1.2"
|
||||
}
|
||||
}
|
||||
|
979
frontend/web/pnpm-lock.yaml
generated
979
frontend/web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -6,18 +6,18 @@ import { GetShortcutAnalyticsResponse } from "@/types/proto/api/v2/shortcut_serv
|
||||
import Icon from "./Icon";
|
||||
|
||||
interface Props {
|
||||
shortcutName: string;
|
||||
shortcutId: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const AnalyticsView: React.FC<Props> = (props: Props) => {
|
||||
const { shortcutName, className } = props;
|
||||
const { shortcutId, className } = props;
|
||||
const { t } = useTranslation();
|
||||
const [analytics, setAnalytics] = useState<GetShortcutAnalyticsResponse | null>(null);
|
||||
const [selectedDeviceTab, setSelectedDeviceTab] = useState<"os" | "browser">("browser");
|
||||
|
||||
useEffect(() => {
|
||||
shortcutServiceClient.getShortcutAnalytics({ name: shortcutName }).then((response) => {
|
||||
shortcutServiceClient.getShortcutAnalytics({ id: shortcutId }).then((response) => {
|
||||
setAnalytics(response);
|
||||
});
|
||||
}, []);
|
||||
|
@ -62,7 +62,7 @@ const CollectionView = (props: Props) => {
|
||||
<div className="bg-gray-100 dark:bg-zinc-800 px-3 py-2 w-full flex flex-row justify-between items-center rounded-t-lg">
|
||||
<div className="w-auto flex flex-col justify-start items-start mr-2">
|
||||
<div className="w-full truncate">
|
||||
<Link className="leading-6 font-medium dark:text-gray-400" to={`/c/${collection.name}`}>
|
||||
<Link className="leading-6 font-medium dark:text-gray-400" to={`/c/${collection.name}`} unstable_viewTransition>
|
||||
{collection.title}
|
||||
</Link>
|
||||
<span className="ml-1 leading-6 text-gray-500 dark:text-gray-400" onClick={handleCopyCollectionLink}>
|
||||
|
@ -25,7 +25,7 @@ import Icon from "./Icon";
|
||||
import ResourceNameInput from "./ResourceNameInput";
|
||||
|
||||
interface Props {
|
||||
shortcutName?: string;
|
||||
shortcutId?: number;
|
||||
initialShortcut?: Partial<Shortcut>;
|
||||
onClose: () => void;
|
||||
onConfirm?: () => void;
|
||||
@ -36,7 +36,7 @@ interface State {
|
||||
}
|
||||
|
||||
const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
||||
const { onClose, onConfirm, shortcutName, initialShortcut } = props;
|
||||
const { onClose, onConfirm, shortcutId, initialShortcut } = props;
|
||||
const { t } = useTranslation();
|
||||
const [state, setState] = useState<State>({
|
||||
shortcutCreate: Shortcut.fromPartial({
|
||||
@ -54,13 +54,13 @@ const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
||||
const shortcutList = shortcutStore.getShortcutList();
|
||||
const [tag, setTag] = useState<string>("");
|
||||
const tagSuggestions = uniq(shortcutList.map((shortcut) => shortcut.tags).flat());
|
||||
const isCreating = isUndefined(shortcutName);
|
||||
const isCreating = isUndefined(shortcutId);
|
||||
const loadingState = useLoading(!isCreating);
|
||||
const requestState = useLoading(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (shortcutName) {
|
||||
const shortcut = shortcutStore.getShortcutByName(shortcutName);
|
||||
if (shortcutId) {
|
||||
const shortcut = shortcutStore.getShortcutById(shortcutId);
|
||||
if (shortcut) {
|
||||
setState({
|
||||
...state,
|
||||
@ -77,7 +77,7 @@ const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
||||
loadingState.setFinish();
|
||||
}
|
||||
}
|
||||
}, [shortcutName]);
|
||||
}, [shortcutId]);
|
||||
|
||||
if (loadingState.isLoading) {
|
||||
return null;
|
||||
@ -183,16 +183,14 @@ const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
||||
}
|
||||
|
||||
try {
|
||||
if (shortcutName) {
|
||||
if (shortcutId) {
|
||||
const originShortcut = shortcutStore.getShortcutById(shortcutId);
|
||||
const updatingShortcut = {
|
||||
...state.shortcutCreate,
|
||||
name: shortcutName,
|
||||
id: shortcutId,
|
||||
tags: tag.split(" ").filter(Boolean),
|
||||
};
|
||||
await shortcutStore.updateShortcut(
|
||||
updatingShortcut,
|
||||
getShortcutUpdateMask(shortcutStore.getShortcutByName(updatingShortcut.name), updatingShortcut)
|
||||
);
|
||||
await shortcutStore.updateShortcut(updatingShortcut, getShortcutUpdateMask(originShortcut, updatingShortcut));
|
||||
} else {
|
||||
await shortcutStore.createShortcut({
|
||||
...state.shortcutCreate,
|
||||
|
@ -19,7 +19,8 @@ const Header: React.FC = () => {
|
||||
const [showAboutDialog, setShowAboutDialog] = useState<boolean>(false);
|
||||
const profile = workspaceStore.profile;
|
||||
const isAdmin = currentUser.role === Role.ADMIN;
|
||||
const shouldShowRouterSwitch = location.pathname === "/" || location.pathname === "/collections";
|
||||
const shouldShowRouterSwitch = location.pathname === "/" || location.pathname === "/collections" || location.pathname === "/memos";
|
||||
const selectedSection = location.pathname === "/" ? "Shortcuts" : location.pathname === "/collections" ? "Collections" : "Memos";
|
||||
|
||||
const handleSignOutButtonClick = async () => {
|
||||
await authServiceClient.signOut({});
|
||||
@ -31,7 +32,7 @@ const Header: React.FC = () => {
|
||||
<div className="w-full bg-gray-50 dark:bg-zinc-800 border-b border-b-gray-200 dark:border-b-zinc-800">
|
||||
<div className="w-full max-w-8xl mx-auto px-3 md:px-12 py-3 flex flex-row justify-between items-center">
|
||||
<div className="flex flex-row justify-start items-center shrink mr-2">
|
||||
<Link to="/" className="cursor-pointer flex flex-row justify-start items-center dark:text-gray-400">
|
||||
<Link to="/" className="cursor-pointer flex flex-row justify-start items-center dark:text-gray-400" unstable_viewTransition>
|
||||
<img id="logo-img" src="/logo.png" className="w-7 h-auto mr-2 -mt-0.5 dark:opacity-80 rounded-full shadow" alt="" />
|
||||
Slash
|
||||
</Link>
|
||||
@ -46,7 +47,7 @@ const Header: React.FC = () => {
|
||||
<Dropdown
|
||||
trigger={
|
||||
<button className="flex flex-row justify-end items-center cursor-pointer">
|
||||
<span className="dark:text-gray-400">{location.pathname === "/" ? "Shortcuts" : "Collections"}</span>
|
||||
<span className="dark:text-gray-400">{selectedSection}</span>
|
||||
<Icon.ChevronsUpDown className="ml-1 w-4 h-auto text-gray-600 dark:text-gray-400" />
|
||||
</button>
|
||||
}
|
||||
@ -54,14 +55,16 @@ const Header: React.FC = () => {
|
||||
actions={
|
||||
<>
|
||||
<Link
|
||||
to="/"
|
||||
className="w-full px-2 flex flex-row justify-start items-center text-left dark:text-gray-400 leading-8 cursor-pointer rounded hover:bg-gray-100 dark:hover:bg-zinc-800 disabled:cursor-not-allowed disabled:bg-gray-100 disabled:opacity-60"
|
||||
to="/"
|
||||
unstable_viewTransition
|
||||
>
|
||||
<Icon.SquareSlash className="w-4 h-auto mr-2 opacity-70" /> Shortcuts
|
||||
</Link>
|
||||
<Link
|
||||
to="/collections"
|
||||
className="w-full px-2 flex flex-row justify-start items-center text-left dark:text-gray-400 leading-8 cursor-pointer rounded hover:bg-gray-100 dark:hover:bg-zinc-800 disabled:cursor-not-allowed disabled:bg-gray-100 disabled:opacity-60"
|
||||
to="/collections"
|
||||
unstable_viewTransition
|
||||
>
|
||||
<Icon.LibrarySquare className="w-4 h-auto mr-2 opacity-70" /> Collections
|
||||
</Link>
|
||||
@ -84,15 +87,17 @@ const Header: React.FC = () => {
|
||||
actions={
|
||||
<>
|
||||
<Link
|
||||
to="/setting/general"
|
||||
className="w-full px-2 flex flex-row justify-start items-center text-left dark:text-gray-400 leading-8 cursor-pointer rounded hover:bg-gray-100 dark:hover:bg-zinc-800 disabled:cursor-not-allowed disabled:bg-gray-100 disabled:opacity-60"
|
||||
to="/setting/general"
|
||||
unstable_viewTransition
|
||||
>
|
||||
<Icon.User className="w-4 h-auto mr-2 opacity-70" /> {t("user.profile")}
|
||||
</Link>
|
||||
{isAdmin && (
|
||||
<Link
|
||||
to="/setting/workspace"
|
||||
className="w-full px-2 flex flex-row justify-start items-center text-left dark:text-gray-400 leading-8 cursor-pointer rounded hover:bg-gray-100 dark:hover:bg-zinc-800 disabled:cursor-not-allowed disabled:bg-gray-100 disabled:opacity-60"
|
||||
to="/setting/workspace"
|
||||
unstable_viewTransition
|
||||
>
|
||||
<Icon.Settings className="w-4 h-auto mr-2 opacity-70" /> {t("settings.self")}
|
||||
</Link>
|
||||
|
@ -31,7 +31,7 @@ const ShortcutActionsDropdown = (props: Props) => {
|
||||
content: `Are you sure to delete shortcut \`${shortcut.name}\`? You cannot undo this action.`,
|
||||
style: "danger",
|
||||
onConfirm: async () => {
|
||||
await shortcutStore.deleteShortcut(shortcut.name);
|
||||
await shortcutStore.deleteShortcut(shortcut.id);
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -82,7 +82,7 @@ const ShortcutActionsDropdown = (props: Props) => {
|
||||
|
||||
{showEditDrawer && (
|
||||
<CreateShortcutDrawer
|
||||
shortcutName={shortcut.name}
|
||||
shortcutId={shortcut.id}
|
||||
onClose={() => setShowEditDrawer(false)}
|
||||
onConfirm={() => setShowEditDrawer(false)}
|
||||
/>
|
||||
|
@ -38,8 +38,9 @@ const ShortcutCard = (props: Props) => {
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<div className="w-[calc(100%-16px)] flex flex-row justify-start items-center mr-1 shrink-0">
|
||||
<Link
|
||||
to={`/shortcut/${shortcut.name}`}
|
||||
className={classNames("w-8 h-8 flex justify-center items-center overflow-clip shrink-0")}
|
||||
to={`/shortcut/${shortcut.id}`}
|
||||
unstable_viewTransition
|
||||
>
|
||||
{favicon ? (
|
||||
<img className="w-full h-auto rounded" src={favicon} decoding="async" loading="lazy" />
|
||||
@ -123,8 +124,9 @@ const ShortcutCard = (props: Props) => {
|
||||
</Tooltip>
|
||||
<Tooltip title="View count" variant="solid" placement="top" arrow>
|
||||
<Link
|
||||
to={`/shortcut/${shortcut.name}#analytics`}
|
||||
className="w-auto leading-5 flex flex-row justify-start items-center flex-nowrap whitespace-nowrap cursor-pointer text-gray-400 text-sm"
|
||||
to={`/shortcut/${shortcut.id}#analytics`}
|
||||
unstable_viewTransition
|
||||
>
|
||||
<Icon.BarChart2 className="w-4 h-auto mr-1 opacity-70" />
|
||||
{t("shortcut.visits", { count: shortcut.viewCount })}
|
||||
|
@ -51,15 +51,13 @@ const Root: React.FC = () => {
|
||||
}, [currentUserSetting]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isInitialized && (
|
||||
<div className="w-full h-auto flex flex-col justify-start items-start dark:bg-zinc-900">
|
||||
<Header />
|
||||
<Navigator />
|
||||
<Outlet />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
isInitialized && (
|
||||
<div className="w-full h-auto flex flex-col justify-start items-start dark:bg-zinc-900">
|
||||
<Header />
|
||||
<Navigator />
|
||||
<Outlet />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import "@fontsource/inter";
|
||||
import { CssVarsProvider } from "@mui/joy";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
|
@ -36,7 +36,7 @@ const CollectionSpace = () => {
|
||||
setShortcuts([]);
|
||||
for (const shortcutId of collection.shortcutIds) {
|
||||
try {
|
||||
const shortcut = await shortcutStore.fetchShortcutById(shortcutId);
|
||||
const shortcut = await shortcutStore.getOrFetchShortcutById(shortcutId);
|
||||
setShortcuts((shortcuts) => {
|
||||
return [...shortcuts, shortcut];
|
||||
});
|
||||
|
@ -28,11 +28,11 @@ interface State {
|
||||
const ShortcutDetail = () => {
|
||||
const { t } = useTranslation();
|
||||
const params = useParams();
|
||||
const shortcutName = params["*"] || "";
|
||||
const shortcutId = Number(params["shortcutId"]);
|
||||
const navigateTo = useNavigateTo();
|
||||
const shortcutStore = useShortcutStore();
|
||||
const userStore = useUserStore();
|
||||
const shortcut = shortcutStore.getShortcutByName(shortcutName);
|
||||
const shortcut = shortcutStore.getShortcutById(shortcutId);
|
||||
const currentUser = useUserStore().getCurrentUser();
|
||||
const [state, setState] = useState<State>({
|
||||
showEditDrawer: false,
|
||||
@ -46,11 +46,11 @@ const ShortcutDetail = () => {
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const shortcut = await shortcutStore.getOrFetchShortcutByName(shortcutName);
|
||||
const shortcut = await shortcutStore.getOrFetchShortcutById(shortcutId);
|
||||
await userStore.getOrFetchUserById(shortcut.creatorId);
|
||||
loadingState.setFinish();
|
||||
})();
|
||||
}, [shortcutName]);
|
||||
}, [shortcutId]);
|
||||
|
||||
if (loadingState.isLoading) {
|
||||
return null;
|
||||
@ -67,7 +67,7 @@ const ShortcutDetail = () => {
|
||||
content: `Are you sure to delete shortcut \`${shortcut.name}\`? You cannot undo this action.`,
|
||||
style: "danger",
|
||||
onConfirm: async () => {
|
||||
await shortcutStore.deleteShortcut(shortcut.name);
|
||||
await shortcutStore.deleteShortcut(shortcut.id);
|
||||
navigateTo("/", {
|
||||
replace: true,
|
||||
});
|
||||
@ -198,7 +198,7 @@ const ShortcutDetail = () => {
|
||||
<Icon.BarChart2 className="w-6 h-auto mr-1" />
|
||||
{t("analytics.self")}
|
||||
</h3>
|
||||
<AnalyticsView className="mt-4 w-full grid grid-cols-1 sm:grid-cols-2 gap-2 sm:gap-4" shortcutName={shortcut.name} />
|
||||
<AnalyticsView className="mt-4 w-full grid grid-cols-1 sm:grid-cols-2 gap-2 sm:gap-4" shortcutId={shortcut.id} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -206,7 +206,7 @@ const ShortcutDetail = () => {
|
||||
|
||||
{state.showEditDrawer && (
|
||||
<CreateShortcutDrawer
|
||||
shortcutName={shortcut.name}
|
||||
shortcutId={shortcut.id}
|
||||
onClose={() =>
|
||||
setState({
|
||||
...state,
|
||||
|
@ -1,19 +1,21 @@
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { isURL } from "@/helpers/utils";
|
||||
import useShortcutStore from "@/stores/v1/shortcut";
|
||||
import { Shortcut } from "@/types/proto/api/v2/shortcut_service";
|
||||
|
||||
const ShortcutSpace = () => {
|
||||
const params = useParams();
|
||||
const shortcutName = params["*"] || "";
|
||||
const shortcutStore = useShortcutStore();
|
||||
const shortcut = shortcutStore.getShortcutByName(shortcutName);
|
||||
const [shortcut, setShortcut] = useState<Shortcut>();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
await shortcutStore.getOrFetchShortcutByName(shortcutName, true);
|
||||
const shortcut = await shortcutStore.fetchShortcutByName(shortcutName);
|
||||
setShortcut(shortcut);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
toast.error(error.details);
|
||||
|
@ -100,7 +100,7 @@ const SignIn: React.FC = () => {
|
||||
{workspaceStore.profile.enableSignup && (
|
||||
<p className="w-full mt-4 text-sm">
|
||||
<span className="dark:text-gray-500">{"Don't have an account yet?"}</span>
|
||||
<Link to="/auth/signup" className="cursor-pointer ml-2 text-blue-600 hover:underline">
|
||||
<Link className="cursor-pointer ml-2 text-blue-600 hover:underline" to="/auth/signup" unstable_viewTransition>
|
||||
{t("auth.sign-up")}
|
||||
</Link>
|
||||
</p>
|
||||
|
@ -115,7 +115,7 @@ const SignUp: React.FC = () => {
|
||||
</form>
|
||||
<p className="w-full mt-4 text-sm">
|
||||
<span className="dark:text-gray-500">{"Already has an account?"}</span>
|
||||
<Link to="/auth" className="cursor-pointer ml-2 text-blue-600 hover:underline">
|
||||
<Link className="cursor-pointer ml-2 text-blue-600 hover:underline" to="/auth" unstable_viewTransition>
|
||||
{t("auth.sign-in")}
|
||||
</Link>
|
||||
</p>
|
||||
|
@ -73,7 +73,10 @@ const SubscriptionSetting: React.FC = () => {
|
||||
<div className="max-w-4xl mx-auto mb-12">
|
||||
<Alert className="!inline-block mb-12">
|
||||
Slash is open source bookmarks and link sharing platform. Our source code is available and accessible on{" "}
|
||||
<Link href="https://github.com/yourselfhosted/slash">GitHub</Link> so anyone can get it, inspect it and review it.
|
||||
<Link href="https://github.com/yourselfhosted/slash" target="_blank">
|
||||
GitHub
|
||||
</Link>{" "}
|
||||
so anyone can get it, inspect it and review it.
|
||||
</Alert>
|
||||
</div>
|
||||
<div className="w-full grid grid-cols-1 gap-6 lg:gap-12 mt-8 md:grid-cols-3 md:max-w-4xl mx-auto">
|
||||
|
@ -35,7 +35,7 @@ const WorkspaceSetting: React.FC = () => {
|
||||
<div className="mt-2">
|
||||
<span className="text-gray-500 mr-2">Current plan:</span>
|
||||
<span className="text-2xl mr-4 dark:text-gray-400">{stringifyPlanType(profile.plan)}</span>
|
||||
<Link to="/setting/subscription">
|
||||
<Link to="/setting/subscription" unstable_viewTransition>
|
||||
<Button size="sm" variant="outlined" startDecorator={<Icon.Settings className="w-4 h-auto" />}>
|
||||
Manage
|
||||
</Button>
|
||||
|
@ -39,7 +39,7 @@ const router = createBrowserRouter([
|
||||
element: <CollectionDashboard />,
|
||||
},
|
||||
{
|
||||
path: "/shortcut/*",
|
||||
path: "/shortcut/:shortcutId",
|
||||
element: <ShortcutDetail />,
|
||||
},
|
||||
{
|
||||
|
@ -1,100 +1,99 @@
|
||||
import { isEqual } from "lodash-es";
|
||||
import { create } from "zustand";
|
||||
import { combine } from "zustand/middleware";
|
||||
import { shortcutServiceClient } from "@/grpcweb";
|
||||
import { Shortcut } from "@/types/proto/api/v2/shortcut_service";
|
||||
|
||||
interface ShortcutState {
|
||||
shortcutMapByName: Record<string, Shortcut>;
|
||||
fetchShortcutList: () => Promise<Shortcut[]>;
|
||||
fetchShortcutById: (id: number) => Promise<Shortcut>;
|
||||
getOrFetchShortcutByName: (name: string, recordView?: boolean) => Promise<Shortcut>;
|
||||
getShortcutByName: (name: string) => Shortcut;
|
||||
getShortcutList: () => Shortcut[];
|
||||
createShortcut: (shortcut: Shortcut) => Promise<Shortcut>;
|
||||
updateShortcut: (shortcut: Partial<Shortcut>, updateMask: string[]) => Promise<Shortcut>;
|
||||
deleteShortcut: (name: string) => Promise<void>;
|
||||
interface State {
|
||||
shortcutMapById: Record<number, Shortcut>;
|
||||
}
|
||||
|
||||
const useShortcutStore = create<ShortcutState>()((set, get) => ({
|
||||
shortcutMapById: {},
|
||||
shortcutMapByName: {},
|
||||
fetchShortcutList: async () => {
|
||||
const { shortcuts } = await shortcutServiceClient.listShortcuts({});
|
||||
const shortcutMap = get().shortcutMapByName;
|
||||
shortcuts.forEach((shortcut) => {
|
||||
shortcutMap[shortcut.name] = shortcut;
|
||||
});
|
||||
set(shortcutMap);
|
||||
return shortcuts;
|
||||
},
|
||||
fetchShortcutById: async (id: number) => {
|
||||
const { shortcut } = await shortcutServiceClient.getShortcutById({
|
||||
id: id,
|
||||
});
|
||||
if (!shortcut) {
|
||||
throw new Error(`Shortcut with id ${id} not found`);
|
||||
}
|
||||
return shortcut;
|
||||
},
|
||||
getOrFetchShortcutByName: async (name: string, recordView = false) => {
|
||||
const shortcutMap = get().shortcutMapByName;
|
||||
if (shortcutMap[name]) {
|
||||
return shortcutMap[name] as Shortcut;
|
||||
}
|
||||
const getDefaultState = (): State => {
|
||||
return {
|
||||
shortcutMapById: {},
|
||||
};
|
||||
};
|
||||
|
||||
const { shortcut } = await shortcutServiceClient.getShortcut({
|
||||
name,
|
||||
recordView,
|
||||
});
|
||||
if (!shortcut) {
|
||||
throw new Error(`Shortcut with name ${name} not found`);
|
||||
}
|
||||
const useShortcutStore = create(
|
||||
combine(getDefaultState(), (set, get) => ({
|
||||
fetchShortcutList: async () => {
|
||||
const { shortcuts } = await shortcutServiceClient.listShortcuts({});
|
||||
const shortcutMap = get().shortcutMapById;
|
||||
shortcuts.forEach((shortcut) => {
|
||||
shortcutMap[shortcut.id] = shortcut;
|
||||
});
|
||||
set({ shortcutMapById: shortcutMap });
|
||||
return shortcuts;
|
||||
},
|
||||
fetchShortcutByName: async (name: string) => {
|
||||
const { shortcut } = await shortcutServiceClient.getShortcutByName({
|
||||
name,
|
||||
});
|
||||
if (!shortcut) {
|
||||
throw new Error(`Shortcut with name ${name} not found`);
|
||||
}
|
||||
return shortcut;
|
||||
},
|
||||
getOrFetchShortcutById: async (id: number, recordView = false) => {
|
||||
const shortcutMap = get().shortcutMapById;
|
||||
if (shortcutMap[id]) {
|
||||
return shortcutMap[id] as Shortcut;
|
||||
}
|
||||
|
||||
shortcutMap[name] = shortcut;
|
||||
set(shortcutMap);
|
||||
return shortcut;
|
||||
},
|
||||
getShortcutByName: (name: string) => {
|
||||
const shortcutMap = get().shortcutMapByName;
|
||||
return shortcutMap[name] || unknownShortcut;
|
||||
},
|
||||
getShortcutList: () => {
|
||||
return Object.values(get().shortcutMapByName);
|
||||
},
|
||||
createShortcut: async (shortcut: Shortcut) => {
|
||||
const { shortcut: createdShortcut } = await shortcutServiceClient.createShortcut({
|
||||
shortcut: shortcut,
|
||||
});
|
||||
if (!createdShortcut) {
|
||||
throw new Error(`Failed to create shortcut`);
|
||||
}
|
||||
const shortcutMap = get().shortcutMapByName;
|
||||
shortcutMap[createdShortcut.name] = createdShortcut;
|
||||
set(shortcutMap);
|
||||
return createdShortcut;
|
||||
},
|
||||
updateShortcut: async (shortcut: Partial<Shortcut>, updateMask: string[]) => {
|
||||
const { shortcut: updatedShortcut } = await shortcutServiceClient.updateShortcut({
|
||||
shortcut: shortcut,
|
||||
updateMask,
|
||||
});
|
||||
if (!updatedShortcut) {
|
||||
throw new Error(`Failed to update shortcut`);
|
||||
}
|
||||
const shortcutMap = get().shortcutMapByName;
|
||||
shortcutMap[updatedShortcut.name] = updatedShortcut;
|
||||
set(shortcutMap);
|
||||
return updatedShortcut;
|
||||
},
|
||||
deleteShortcut: async (name: string) => {
|
||||
await shortcutServiceClient.deleteShortcut({
|
||||
name,
|
||||
});
|
||||
const shortcutMap = get().shortcutMapByName;
|
||||
delete shortcutMap[name];
|
||||
set(shortcutMap);
|
||||
},
|
||||
}));
|
||||
const { shortcut } = await shortcutServiceClient.getShortcut({
|
||||
id,
|
||||
recordView,
|
||||
});
|
||||
if (!shortcut) {
|
||||
throw new Error(`Shortcut with id ${id} not found`);
|
||||
}
|
||||
|
||||
shortcutMap[id] = shortcut;
|
||||
set({ shortcutMapById: shortcutMap });
|
||||
return shortcut;
|
||||
},
|
||||
getShortcutById: (id: number) => {
|
||||
const shortcutMap = get().shortcutMapById;
|
||||
return shortcutMap[id] || unknownShortcut;
|
||||
},
|
||||
getShortcutList: () => {
|
||||
return Object.values(get().shortcutMapById);
|
||||
},
|
||||
createShortcut: async (shortcut: Shortcut) => {
|
||||
const { shortcut: createdShortcut } = await shortcutServiceClient.createShortcut({
|
||||
shortcut: shortcut,
|
||||
});
|
||||
if (!createdShortcut) {
|
||||
throw new Error(`Failed to create shortcut`);
|
||||
}
|
||||
const shortcutMap = get().shortcutMapById;
|
||||
shortcutMap[createdShortcut.id] = createdShortcut;
|
||||
set({ shortcutMapById: shortcutMap });
|
||||
return createdShortcut;
|
||||
},
|
||||
updateShortcut: async (shortcut: Partial<Shortcut>, updateMask: string[]) => {
|
||||
const { shortcut: updatedShortcut } = await shortcutServiceClient.updateShortcut({
|
||||
shortcut: shortcut,
|
||||
updateMask,
|
||||
});
|
||||
if (!updatedShortcut) {
|
||||
throw new Error(`Failed to update shortcut`);
|
||||
}
|
||||
const shortcutMap = get().shortcutMapById;
|
||||
shortcutMap[updatedShortcut.id] = updatedShortcut;
|
||||
set({ shortcutMapById: shortcutMap });
|
||||
return updatedShortcut;
|
||||
},
|
||||
deleteShortcut: async (id: number) => {
|
||||
await shortcutServiceClient.deleteShortcut({
|
||||
id,
|
||||
});
|
||||
const shortcutMap = get().shortcutMapById;
|
||||
delete shortcutMap[id];
|
||||
set({ shortcutMapById: shortcutMap });
|
||||
},
|
||||
}))
|
||||
);
|
||||
|
||||
const unknownShortcut: Shortcut = Shortcut.fromPartial({
|
||||
id: -1,
|
||||
|
Reference in New Issue
Block a user