-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3a085f3
commit 2db86f6
Showing
8 changed files
with
238 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import { Input, Textarea } from "@mui/joy"; | ||
import { Button } from "@usememos/mui"; | ||
import { XIcon } from "lucide-react"; | ||
import React, { useState } from "react"; | ||
import { toast } from "react-hot-toast"; | ||
import { userServiceClient } from "@/grpcweb"; | ||
import useCurrentUser from "@/hooks/useCurrentUser"; | ||
import useLoading from "@/hooks/useLoading"; | ||
import { useUserStore } from "@/store/v1"; | ||
import { Shortcut } from "@/types/proto/api/v1/user_service"; | ||
import { useTranslate } from "@/utils/i18n"; | ||
import { generateUUID } from "@/utils/uuid"; | ||
import { generateDialog } from "./Dialog"; | ||
|
||
interface Props extends DialogProps { | ||
shortcut?: Shortcut; | ||
} | ||
|
||
const CreateShortcutDialog: React.FC<Props> = (props: Props) => { | ||
const { destroy } = props; | ||
const t = useTranslate(); | ||
const user = useCurrentUser(); | ||
const userStore = useUserStore(); | ||
const [shortcut, setShortcut] = useState(Shortcut.fromPartial({ ...props.shortcut })); | ||
const requestState = useLoading(false); | ||
const isCreating = !props.shortcut; | ||
|
||
const onShortcutTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
setShortcut({ ...shortcut, title: e.target.value }); | ||
}; | ||
|
||
const onShortcutFilterChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { | ||
setShortcut({ ...shortcut, filter: e.target.value }); | ||
}; | ||
|
||
const handleConfirm = async () => { | ||
if (!shortcut.title || !shortcut.filter) { | ||
toast.error("Title and filter cannot be empty"); | ||
return; | ||
} | ||
|
||
try { | ||
if (isCreating) { | ||
await userServiceClient.createShortcut({ | ||
parent: user.name, | ||
shortcut: { | ||
...shortcut, | ||
id: generateUUID(), | ||
}, | ||
}); | ||
toast.success("Create shortcut successfully"); | ||
} else { | ||
await userServiceClient.updateShortcut({ parent: user.name, shortcut, updateMask: ["title", "filter"] }); | ||
toast.success("Update shortcut successfully"); | ||
} | ||
// Refresh shortcuts. | ||
await userStore.fetchShortcuts(); | ||
destroy(); | ||
} catch (error: any) { | ||
console.error(error); | ||
toast.error(error.details); | ||
} | ||
}; | ||
|
||
return ( | ||
<> | ||
<div className="dialog-header-container"> | ||
<p className="title-text">{`${isCreating ? "Create" : "Edit"} Shortcut`}</p> | ||
<Button size="sm" variant="plain" onClick={() => destroy()}> | ||
<XIcon className="w-5 h-auto" /> | ||
</Button> | ||
</div> | ||
<div className="dialog-content-container max-w-md min-w-72"> | ||
<div className="w-full flex flex-col justify-start items-start mb-3"> | ||
<span className="text-sm whitespace-nowrap mb-1">Title</span> | ||
<Input className="w-full" type="text" placeholder="" value={shortcut.title} onChange={onShortcutTitleChange} /> | ||
<span className="text-sm whitespace-nowrap mt-3 mb-1">Filter</span> | ||
<Textarea | ||
className="w-full" | ||
minRows={3} | ||
maxRows={5} | ||
size="sm" | ||
placeholder={"Shortcut filter"} | ||
value={shortcut.filter} | ||
onChange={onShortcutFilterChange} | ||
/> | ||
</div> | ||
<div className="w-full opacity-70"> | ||
<p className="text-sm">{t("common.learn-more")}:</p> | ||
<ul className="list-disc list-inside text-sm pl-2 mt-1"> | ||
<li> | ||
<a | ||
className="text-sm text-blue-600 hover:underline" | ||
href="https://www.usememos.com/docs/getting-started/shortcuts" | ||
target="_blank" | ||
> | ||
Docs - Shortcuts | ||
</a> | ||
</li> | ||
<li> | ||
<a | ||
className="text-sm text-blue-600 hover:underline" | ||
href="https://www.usememos.com/docs/getting-started/shortcuts#how-to-write-a-filter-in-a-shortcut" | ||
target="_blank" | ||
> | ||
How to Write a Filter in a Shortcut? | ||
</a> | ||
</li> | ||
</ul> | ||
</div> | ||
<div className="w-full flex flex-row justify-end items-center space-x-2 mt-2"> | ||
<Button variant="plain" disabled={requestState.isLoading} onClick={destroy}> | ||
{t("common.cancel")} | ||
</Button> | ||
<Button color="primary" disabled={requestState.isLoading} onClick={handleConfirm}> | ||
{t("common.confirm")} | ||
</Button> | ||
</div> | ||
</div> | ||
</> | ||
); | ||
}; | ||
|
||
function showCreateShortcutDialog(props: Pick<Props, "shortcut">) { | ||
generateDialog( | ||
{ | ||
className: "create-shortcut-dialog", | ||
dialogName: "create-shortcut-dialog", | ||
}, | ||
CreateShortcutDialog, | ||
props, | ||
); | ||
} | ||
|
||
export default showCreateShortcutDialog; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { Dropdown, Menu, MenuButton, MenuItem, Tooltip } from "@mui/joy"; | ||
import { Edit3Icon, MoreVerticalIcon, TrashIcon, PlusIcon } from "lucide-react"; | ||
import { userServiceClient } from "@/grpcweb"; | ||
import useAsyncEffect from "@/hooks/useAsyncEffect"; | ||
import useCurrentUser from "@/hooks/useCurrentUser"; | ||
import { useMemoFilterStore, useUserStore } from "@/store/v1"; | ||
import { Shortcut } from "@/types/proto/api/v1/user_service"; | ||
import { cn } from "@/utils"; | ||
import { useTranslate } from "@/utils/i18n"; | ||
import showCreateShortcutDialog from "../CreateShortcutDialog"; | ||
|
||
const ShortcutsSection = () => { | ||
const t = useTranslate(); | ||
const user = useCurrentUser(); | ||
const userStore = useUserStore(); | ||
const memoFilterStore = useMemoFilterStore(); | ||
const shortcuts = userStore.getState().shortcuts; | ||
|
||
useAsyncEffect(async () => { | ||
await userStore.fetchShortcuts(); | ||
}, []); | ||
|
||
const handleDeleteShortcut = async (shortcut: Shortcut) => { | ||
const confirmed = window.confirm("Are you sure you want to delete this shortcut?"); | ||
if (confirmed) { | ||
await userServiceClient.deleteShortcut({ parent: user.name, id: shortcut.id }); | ||
await userStore.fetchShortcuts(); | ||
} | ||
}; | ||
|
||
return ( | ||
<div className="w-full flex flex-col justify-start items-start mt-3 px-1 h-auto shrink-0 flex-nowrap hide-scrollbar"> | ||
<div className="flex flex-row justify-between items-center w-full gap-1 mb-1 text-sm leading-6 text-gray-400 select-none"> | ||
<span>{t("common.shortcuts")}</span> | ||
<Tooltip title={t("common.create")} placement="top"> | ||
<PlusIcon className="w-4 h-auto" onClick={() => showCreateShortcutDialog({})} /> | ||
</Tooltip> | ||
</div> | ||
<div className="w-full flex flex-row justify-start items-center relative flex-wrap gap-x-2 gap-y-1"> | ||
{shortcuts.map((shortcut) => { | ||
const selected = memoFilterStore.shortcut === shortcut.id; | ||
return ( | ||
<div | ||
key={shortcut.id} | ||
className="shrink-0 w-full text-sm rounded-md leading-6 flex flex-row justify-between items-center select-none gap-2 text-gray-600 dark:text-gray-400 dark:border-zinc-800" | ||
> | ||
<span | ||
className={cn("truncate cursor-pointer dark:opacity-80", selected && "font-medium underline")} | ||
onClick={() => (selected ? memoFilterStore.setShortcut(undefined) : memoFilterStore.setShortcut(shortcut.id))} | ||
> | ||
{shortcut.title} | ||
</span> | ||
<Dropdown> | ||
<MenuButton slots={{ root: "div" }}> | ||
<MoreVerticalIcon className="w-4 h-auto shrink-0 opacity-40" /> | ||
</MenuButton> | ||
<Menu size="sm" placement="bottom-start"> | ||
<MenuItem onClick={() => showCreateShortcutDialog({ shortcut })}> | ||
<Edit3Icon className="w-4 h-auto" /> | ||
{t("common.edit")} | ||
</MenuItem> | ||
<MenuItem color="danger" onClick={() => handleDeleteShortcut(shortcut)}> | ||
<TrashIcon className="w-4 h-auto" /> | ||
{t("common.delete")} | ||
</MenuItem> | ||
</Menu> | ||
</Dropdown> | ||
</div> | ||
); | ||
})} | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default ShortcutsSection; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters