Customize title bar (#1184)
* add basic title bar * add title bar actions * fix layout * update title bar * update layout * fix title bar for macOS * UI * setup menu for macOS * fix title bar logo
This commit is contained in:
@@ -343,7 +343,9 @@
|
||||
"logoutAndRemoveAllPersonalSettings": "Logout and remove all personal settings",
|
||||
"hotkeys": "Hotkeys",
|
||||
"player": "Player",
|
||||
"quit": "Quit",
|
||||
"quitApp": "Quit APP",
|
||||
"quitAppDescription": "Are you sure to quit Enjoy app?",
|
||||
"openPreferences": "Open preferences",
|
||||
"openCopilot": "Open copilot",
|
||||
"playOrPause": "Play or pause",
|
||||
@@ -929,5 +931,7 @@
|
||||
"compressMediaBeforeAdding": "Compress media before adding",
|
||||
"keepOriginalMedia": "Keep original media",
|
||||
"myPronunciation": "My pronunciation",
|
||||
"originalPronunciation": "Original pronunciation"
|
||||
"originalPronunciation": "Original pronunciation",
|
||||
"reloadApp": "reload app",
|
||||
"exit": "Exit"
|
||||
}
|
||||
|
||||
@@ -344,7 +344,9 @@
|
||||
"hotkeys": "快捷键",
|
||||
"system": "系统",
|
||||
"player": "播放器",
|
||||
"quit": "退出",
|
||||
"quitApp": "退出应用",
|
||||
"quitAppDescription": "确定要退出 Enjoy 应用吗?",
|
||||
"openPreferences": "打开设置",
|
||||
"openCopilot": "打开 Copilot",
|
||||
"playOrPause": "播放/暂停",
|
||||
@@ -929,5 +931,7 @@
|
||||
"compressMediaBeforeAdding": "添加前压缩媒体",
|
||||
"keepOriginalMedia": "保存原始媒体",
|
||||
"myPronunciation": "我的发音",
|
||||
"originalPronunciation": "原始发音"
|
||||
"originalPronunciation": "原始发音",
|
||||
"reloadApp": "重载应用",
|
||||
"exit": "退出"
|
||||
}
|
||||
|
||||
@@ -94,6 +94,12 @@
|
||||
.scroll {
|
||||
@apply scrollbar-thin scrollbar-thumb-primary scrollbar-track-secondary;
|
||||
}
|
||||
.draggable-region {
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
.non-draggable-region {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
@@ -25,6 +25,8 @@ import dict from "./dict";
|
||||
import mdict from "./mdict";
|
||||
import decompresser from "./decompresser";
|
||||
import { UserSetting } from "@main/db/models";
|
||||
import { platform } from "os";
|
||||
import { t } from "i18next";
|
||||
|
||||
const __filename = url.fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@@ -520,6 +522,14 @@ ${log}
|
||||
preload: path.join(__dirname, "preload.js"),
|
||||
spellcheck: false,
|
||||
},
|
||||
frame: false,
|
||||
titleBarStyle: "hidden",
|
||||
titleBarOverlay: process.platform === "darwin",
|
||||
trafficLightPosition: {
|
||||
x: 10,
|
||||
y: 8,
|
||||
},
|
||||
useContentSize: true,
|
||||
});
|
||||
|
||||
mainWindow.on("ready-to-show", () => {
|
||||
@@ -527,7 +537,62 @@ ${log}
|
||||
});
|
||||
|
||||
mainWindow.on("resize", () => {
|
||||
mainWindow.webContents.send("window-on-resize", mainWindow.getBounds());
|
||||
mainWindow.webContents.send("window-on-change", {
|
||||
event: "resize",
|
||||
state: mainWindow.getBounds(),
|
||||
});
|
||||
});
|
||||
|
||||
mainWindow.on("enter-full-screen", () => {
|
||||
mainWindow.webContents.send("window-on-change", { event: "enter-full-screen" });
|
||||
});
|
||||
|
||||
mainWindow.on("leave-full-screen", () => {
|
||||
mainWindow.webContents.send("window-on-change", { event: "leave-full-screen" });
|
||||
});
|
||||
|
||||
mainWindow.on("maximize", () => {
|
||||
mainWindow.webContents.send("window-on-change", { event: "maximize" });
|
||||
});
|
||||
|
||||
mainWindow.on("unmaximize", () => {
|
||||
mainWindow.webContents.send("window-on-change", { event: "unmaximize" });
|
||||
});
|
||||
|
||||
ipcMain.handle("window-is-maximized", () => {
|
||||
return mainWindow.isMaximized();
|
||||
});
|
||||
|
||||
ipcMain.handle("window-toggle-maximized", () => {
|
||||
if (mainWindow.isMaximized()) {
|
||||
mainWindow.unmaximize();
|
||||
} else {
|
||||
mainWindow.maximize();
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("window-maximize", () => {
|
||||
mainWindow.maximize();
|
||||
});
|
||||
|
||||
ipcMain.handle("window-unmaximize", () => {
|
||||
mainWindow.unmaximize();
|
||||
});
|
||||
|
||||
ipcMain.handle("window-fullscreen", () => {
|
||||
mainWindow.setFullScreen(true);
|
||||
});
|
||||
|
||||
ipcMain.handle("window-unfullscreen", () => {
|
||||
mainWindow.setFullScreen(false);
|
||||
});
|
||||
|
||||
ipcMain.handle("window-minimize", () => {
|
||||
mainWindow.minimize();
|
||||
});
|
||||
|
||||
ipcMain.handle("window-close", () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
mainWindow.webContents.setWindowOpenHandler(() => {
|
||||
@@ -571,7 +636,42 @@ ${log}
|
||||
// mainWindow.webContents.openDevTools();
|
||||
}
|
||||
|
||||
Menu.setApplicationMenu(null);
|
||||
if (platform() === "darwin") {
|
||||
const menu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: app.name,
|
||||
submenu: [
|
||||
{ role: "about" },
|
||||
{ type: "separator" },
|
||||
{ role: "hide" },
|
||||
{ role: "unhide" },
|
||||
{ type: "separator" },
|
||||
{ role: "quit" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "&Help",
|
||||
submenu: [
|
||||
{
|
||||
label: "Check for Updates...",
|
||||
click: () => {
|
||||
shell.openExternal("https://1000h.org/enjoy-app/install.html");
|
||||
},
|
||||
},
|
||||
{
|
||||
label: "Report Issue...",
|
||||
click: () => {
|
||||
shell.openExternal(`${REPO_URL}/issues/new`)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
Menu.setApplicationMenu(menu);
|
||||
} else {
|
||||
Menu.setApplicationMenu(null);
|
||||
}
|
||||
|
||||
main.win = mainWindow;
|
||||
};
|
||||
|
||||
@@ -54,14 +54,49 @@ contextBridge.exposeInMainWorld("__ENJOY_APP__", {
|
||||
version,
|
||||
},
|
||||
window: {
|
||||
onResize: (
|
||||
isFullScreen: () => {
|
||||
return ipcRenderer.invoke("window-is-full-screen");
|
||||
},
|
||||
toggleFullscreen: () => {
|
||||
return ipcRenderer.invoke("window-fullscreen");
|
||||
},
|
||||
isMaximized: () => {
|
||||
return ipcRenderer.invoke("window-is-maximized");
|
||||
},
|
||||
toggleMaximized: () => {
|
||||
return ipcRenderer.invoke("window-toggle-maximized");
|
||||
},
|
||||
maximize: () => {
|
||||
return ipcRenderer.invoke("window-maximize");
|
||||
},
|
||||
unmaximize: () => {
|
||||
return ipcRenderer.invoke("window-unmaximize");
|
||||
},
|
||||
fullscreen: () => {
|
||||
return ipcRenderer.invoke("window-fullscreen");
|
||||
},
|
||||
unfullscreen: () => {
|
||||
return ipcRenderer.invoke("window-unfullscreen");
|
||||
},
|
||||
minimize: () => {
|
||||
return ipcRenderer.invoke("window-minimize");
|
||||
},
|
||||
close: () => {
|
||||
return ipcRenderer.invoke("window-close");
|
||||
},
|
||||
onChange: (
|
||||
callback: (
|
||||
event: IpcRendererEvent,
|
||||
bounds: { x: number; y: number; width: number; height: number }
|
||||
state: { event: string; state: any }
|
||||
) => void
|
||||
) => ipcRenderer.on("window-on-resize", callback),
|
||||
removeListeners: () => {
|
||||
ipcRenderer.removeAllListeners("window-on-resize");
|
||||
) => ipcRenderer.on("window-on-change", callback),
|
||||
removeListener: (
|
||||
listener: (event: IpcRendererEvent, ...args: any[]) => void
|
||||
) => {
|
||||
ipcRenderer.removeListener("window-on-change", listener);
|
||||
},
|
||||
removeAllListeners: () => {
|
||||
ipcRenderer.removeAllListeners("window-on-change");
|
||||
},
|
||||
},
|
||||
system: {
|
||||
|
||||
@@ -12,14 +12,14 @@ export const ChatSession = (props: {
|
||||
|
||||
if (!chatId) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-screen">
|
||||
<div className="flex items-center justify-center h-[calc(100vh-2rem)]">
|
||||
<span className="text-muted-foreground">{t("noChatSelected")}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollArea className="h-screen relative">
|
||||
<ScrollArea className="h-[calc(100vh-2rem)] relative">
|
||||
<ChatSessionProvider chatId={chatId}>
|
||||
<ChatHeader
|
||||
sidePanelCollapsed={sidePanelCollapsed}
|
||||
|
||||
@@ -8,7 +8,7 @@ export const CopilotSession = () => {
|
||||
const { currentChat } = useContext(CopilotProviderContext);
|
||||
|
||||
return (
|
||||
<ScrollArea className="h-screen relative">
|
||||
<ScrollArea className="h-[calc(100vh-2rem)] relative">
|
||||
{currentChat?.id ? (
|
||||
<ChatSessionProvider chatId={currentChat.id}>
|
||||
<CopilotHeader />
|
||||
|
||||
@@ -20,3 +20,4 @@ export * from "./users";
|
||||
export * from "./videos";
|
||||
export * from "./widgets";
|
||||
export * from "./login";
|
||||
export * from "./layouts";
|
||||
|
||||
2
enjoy/src/renderer/components/layouts/index.ts
Normal file
2
enjoy/src/renderer/components/layouts/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./layout";
|
||||
export * from "./title-bar";
|
||||
66
enjoy/src/renderer/components/layouts/layout.tsx
Normal file
66
enjoy/src/renderer/components/layouts/layout.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Outlet } from "react-router-dom";
|
||||
import {
|
||||
AppSettingsProviderContext,
|
||||
CopilotProviderContext,
|
||||
} from "@renderer/context";
|
||||
import { useContext } from "react";
|
||||
import { CopilotSession, TitleBar, Sidebar } from "@renderer/components";
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@renderer/components/ui";
|
||||
|
||||
export const Layout = () => {
|
||||
const { initialized } = useContext(AppSettingsProviderContext);
|
||||
const { active, setActive } = useContext(CopilotProviderContext);
|
||||
|
||||
if (initialized) {
|
||||
return (
|
||||
<div className="h-screen flex flex-col">
|
||||
<TitleBar />
|
||||
<ResizablePanelGroup
|
||||
direction="horizontal"
|
||||
className="flex-1 h-full w-full"
|
||||
data-testid="layout-home"
|
||||
>
|
||||
<ResizablePanel id="main-panel" order={1} minSize={50}>
|
||||
<div className="flex flex-start">
|
||||
<Sidebar />
|
||||
<div className="flex-1 border-l overflow-x-hidden overflow-y-auto h-[calc(100vh-2rem)]">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
{active && (
|
||||
<>
|
||||
<ResizableHandle />
|
||||
<ResizablePanel
|
||||
id="copilot-panel"
|
||||
order={2}
|
||||
collapsible={true}
|
||||
defaultSize={30}
|
||||
maxSize={50}
|
||||
minSize={15}
|
||||
onCollapse={() => setActive(false)}
|
||||
>
|
||||
<div className="h-[calc(100vh-2rem)]">
|
||||
<CopilotSession />
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
</>
|
||||
)}
|
||||
</ResizablePanelGroup>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className="h-screen flex flex-col w-full">
|
||||
<TitleBar />
|
||||
<div className="flex-1 h-[calc(100vh-2rem)] overflow-y-auto">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
275
enjoy/src/renderer/components/layouts/title-bar.tsx
Normal file
275
enjoy/src/renderer/components/layouts/title-bar.tsx
Normal file
@@ -0,0 +1,275 @@
|
||||
import {
|
||||
AppSettingsProviderContext,
|
||||
CopilotProviderContext,
|
||||
} from "@/renderer/context";
|
||||
import { ChevronRightIcon } from "@radix-ui/react-icons";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogHeader,
|
||||
AlertDialogContent,
|
||||
AlertDialogTrigger,
|
||||
AlertDialogTitle,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogCancel,
|
||||
AlertDialogAction,
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSeparator,
|
||||
} from "@renderer/components/ui";
|
||||
import { IpcRendererEvent } from "electron/renderer";
|
||||
import { t } from "i18next";
|
||||
import {
|
||||
ExternalLinkIcon,
|
||||
HelpCircleIcon,
|
||||
LightbulbIcon,
|
||||
LightbulbOffIcon,
|
||||
MaximizeIcon,
|
||||
MenuIcon,
|
||||
MinimizeIcon,
|
||||
MinusIcon,
|
||||
SettingsIcon,
|
||||
XIcon,
|
||||
} from "lucide-react";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
|
||||
export const TitleBar = () => {
|
||||
const [isMaximized, setIsMaximized] = useState(false);
|
||||
const [isFullScreen, setIsFullScreen] = useState(false);
|
||||
const [platform, setPlatform] = useState<"darwin" | "win32" | "linux">();
|
||||
|
||||
const { EnjoyApp, setDisplayPreferences, initialized } = useContext(
|
||||
AppSettingsProviderContext
|
||||
);
|
||||
const { active, setActive } = useContext(CopilotProviderContext);
|
||||
|
||||
const onWindowChange = (
|
||||
_event: IpcRendererEvent,
|
||||
state: { event: string }
|
||||
) => {
|
||||
if (state.event === "maximize") {
|
||||
setIsMaximized(true);
|
||||
} else if (state.event === "unmaximize") {
|
||||
setIsMaximized(false);
|
||||
} else if (state.event === "enter-full-screen") {
|
||||
setIsFullScreen(true);
|
||||
} else if (state.event === "leave-full-screen") {
|
||||
setIsFullScreen(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
EnjoyApp.window.onChange(onWindowChange);
|
||||
EnjoyApp.app.getPlatformInfo().then((info) => {
|
||||
setPlatform(info.platform as "darwin" | "win32" | "linux");
|
||||
});
|
||||
|
||||
return () => {
|
||||
EnjoyApp.window.removeListener(onWindowChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="z-50 h-8 w-full bg-muted draggable-region flex items-center justify-between border-b">
|
||||
<div className="flex items-center px-2">
|
||||
{platform === "darwin" && !isFullScreen && <div className="w-16"></div>}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-8 rounded-none non-draggable-region hover:bg-primary/10"
|
||||
>
|
||||
<img src="./assets/icon.png" alt="Enjoy" className="size-5" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" side="bottom">
|
||||
<DropdownMenuItem
|
||||
onClick={() => EnjoyApp.app.reload()}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<span className="capitalize">{t("reloadApp")}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={() => EnjoyApp.window.close()}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<span className="capitalize">{t("exit")}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
{initialized && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-8 rounded-none non-draggable-region hover:bg-primary/10"
|
||||
onClick={() => setDisplayPreferences(true)}
|
||||
>
|
||||
<SettingsIcon className="size-4" />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-8 rounded-none non-draggable-region hover:bg-primary/10"
|
||||
>
|
||||
<HelpCircleIcon className="size-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent
|
||||
className="w-[--radix-dropdown-menu-trigger-width]"
|
||||
align="start"
|
||||
side="top"
|
||||
>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
EnjoyApp.shell.openExternal("https://1000h.org/enjoy-app/")
|
||||
}
|
||||
className="flex justify-between space-x-4"
|
||||
>
|
||||
<span className="min-w-fit capitalize">{t("userGuide")}</span>
|
||||
<ExternalLinkIcon className="size-4" />
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger className="non-draggable-region">
|
||||
<span className="capitalize">
|
||||
{t("feedback")}
|
||||
</span>
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
EnjoyApp.shell.openExternal(
|
||||
"https://mixin.one/codes/f6ff96b8-54fb-4ad8-a6d4-5a5bdb1df13e"
|
||||
)
|
||||
}
|
||||
className="flex justify-between space-x-4"
|
||||
>
|
||||
<span>Mixin</span>
|
||||
<ExternalLinkIcon className="size-4" />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
EnjoyApp.shell.openExternal(
|
||||
"https://github.com/zuodaotech/everyone-can-use-english/discussions"
|
||||
)
|
||||
}
|
||||
className="flex justify-between space-x-4"
|
||||
>
|
||||
<span>Github</span>
|
||||
<ExternalLinkIcon className="size-4" />
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
EnjoyApp.shell.openExternal(
|
||||
"https://1000h.org/enjoy-app/install.html"
|
||||
)
|
||||
}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<span className="capitalize">{t("checkUpdate")}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
{initialized && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className={`size-8 rounded-none non-draggable-region hover:bg-primary/10 ${
|
||||
active ? "bg-primary/10" : ""
|
||||
}`}
|
||||
onClick={() => setActive(!active)}
|
||||
>
|
||||
{active ? (
|
||||
<LightbulbIcon className="size-4" />
|
||||
) : (
|
||||
<LightbulbOffIcon className="size-4" />
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{platform !== "darwin" && (
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-8 rounded-none non-draggable-region hover:bg-primary/10"
|
||||
onClick={() => EnjoyApp.window.minimize()}
|
||||
>
|
||||
<MinusIcon className="size-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-8 rounded-none non-draggable-region hover:bg-primary/10"
|
||||
onClick={() => EnjoyApp.window.toggleMaximized()}
|
||||
>
|
||||
{isMaximized ? (
|
||||
<MinimizeIcon className="size-4" />
|
||||
) : (
|
||||
<MaximizeIcon className="size-4" />
|
||||
)}
|
||||
</Button>
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="size-8 rounded-none non-draggable-region hover:bg-destructive"
|
||||
>
|
||||
<XIcon className="size-4" />
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t("quitApp")}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t("quitAppDescription")}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
onClick={() => EnjoyApp.window.close()}
|
||||
>
|
||||
{t("quit")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -38,7 +38,7 @@ export const LoginForm = () => {
|
||||
|
||||
if (user) {
|
||||
return (
|
||||
<div className="px-4 py-2 border rounded-lg w-full max-w-sm">
|
||||
<div className="px-4 py-2 border rounded-lg w-full max-w-sm m-auto">
|
||||
<UserSettings />
|
||||
<Separator />
|
||||
<LanguageSettings />
|
||||
|
||||
@@ -448,10 +448,8 @@ export const MediaCurrentRecording = () => {
|
||||
debouncedCalContainerSize();
|
||||
});
|
||||
observer.observe(ref.current);
|
||||
EnjoyApp.window.onResize(debouncedCalContainerSize);
|
||||
|
||||
return () => {
|
||||
EnjoyApp.window.removeListeners();
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [ref, player]);
|
||||
|
||||
@@ -144,10 +144,7 @@ export const MediaWaveform = () => {
|
||||
});
|
||||
observer.observe(ref.current);
|
||||
|
||||
EnjoyApp.window.onResize(debouncedCalContainerSize);
|
||||
|
||||
return () => {
|
||||
EnjoyApp.window.removeListeners();
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [ref, wavesurfer]);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from "./db-state";
|
||||
export * from "./layout";
|
||||
export * from "./loader-spin";
|
||||
export * from "./markdown-wrapper";
|
||||
export * from "./no-records-found";
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
import { Sidebar } from "./sidebar";
|
||||
import { Outlet, Link } from "react-router-dom";
|
||||
import {
|
||||
AppSettingsProviderContext,
|
||||
CopilotProviderContext,
|
||||
DbProviderContext,
|
||||
} from "@renderer/context";
|
||||
import { useContext } from "react";
|
||||
import { Button } from "@renderer/components/ui/button";
|
||||
import { DbState, CopilotSession } from "@renderer/components";
|
||||
import { t } from "i18next";
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "@renderer/components/ui";
|
||||
|
||||
export const Layout = () => {
|
||||
const { initialized } = useContext(AppSettingsProviderContext);
|
||||
const db = useContext(DbProviderContext);
|
||||
const { active, setActive } = useContext(CopilotProviderContext);
|
||||
|
||||
if (!initialized) {
|
||||
return (
|
||||
<div
|
||||
className="h-screen flex justify-center items-center"
|
||||
date-testid="layout-onboarding"
|
||||
>
|
||||
<div className="text-center">
|
||||
<div className="text-lg mb-6">
|
||||
{t("welcomeTo")} <span className="font-semibold">Enjoy App</span>
|
||||
</div>
|
||||
|
||||
<div className="">
|
||||
<Link data-testid="landing-button" to="/landing" replace>
|
||||
<Button size="lg">{t("startToUse")}</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (db.state === "connected") {
|
||||
return (
|
||||
<ResizablePanelGroup
|
||||
direction="horizontal"
|
||||
className="h-screen w-full"
|
||||
data-testid="layout-home"
|
||||
>
|
||||
<ResizablePanel id="main-panel" order={1} minSize={50}>
|
||||
<div className="flex flex-start">
|
||||
<Sidebar />
|
||||
<div className="flex-1 border-l overflow-x-hidden overflow-y-auto h-screen">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
{active && (
|
||||
<>
|
||||
<ResizableHandle />
|
||||
<ResizablePanel
|
||||
id="copilot-panel"
|
||||
order={2}
|
||||
collapsible={true}
|
||||
defaultSize={30}
|
||||
maxSize={50}
|
||||
minSize={15}
|
||||
onCollapse={() => setActive(false)}
|
||||
>
|
||||
<div className="h-screen">
|
||||
<CopilotSession />
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
</>
|
||||
)}
|
||||
</ResizablePanelGroup>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
className="h-screen w-screen flex justify-center items-center"
|
||||
data-testid="layout-db-error"
|
||||
>
|
||||
<DbState />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -1,17 +1,11 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
ScrollArea,
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuItem,
|
||||
Separator,
|
||||
DialogTitle,
|
||||
@@ -30,8 +24,6 @@ import {
|
||||
BotIcon,
|
||||
UsersRoundIcon,
|
||||
LucideIcon,
|
||||
HelpCircleIcon,
|
||||
ExternalLinkIcon,
|
||||
NotebookPenIcon,
|
||||
SpeechIcon,
|
||||
GraduationCapIcon,
|
||||
@@ -40,7 +32,6 @@ import {
|
||||
PanelLeftCloseIcon,
|
||||
ChevronsUpDownIcon,
|
||||
LogOutIcon,
|
||||
ChevronRightIcon,
|
||||
} from "lucide-react";
|
||||
import { useLocation, Link, useNavigate } from "react-router-dom";
|
||||
import { t } from "i18next";
|
||||
@@ -53,7 +44,8 @@ import { useState } from "react";
|
||||
export const Sidebar = () => {
|
||||
const location = useLocation();
|
||||
const activeTab = location.pathname;
|
||||
const { EnjoyApp, cable } = useContext(AppSettingsProviderContext);
|
||||
const { EnjoyApp, cable, displayPreferences, setDisplayPreferences } =
|
||||
useContext(AppSettingsProviderContext);
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -83,7 +75,7 @@ export const Sidebar = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`h-[100vh] transition-all relative ${
|
||||
className={`h-[calc(100vh-2rem)] pt-8 transition-all relative draggable-region ${
|
||||
isOpen ? "w-48" : "w-12"
|
||||
}`}
|
||||
data-testid="sidebar"
|
||||
@@ -93,7 +85,7 @@ export const Sidebar = () => {
|
||||
isOpen ? "w-48" : "w-12"
|
||||
}`}
|
||||
>
|
||||
<ScrollArea className="w-full h-full pb-12">
|
||||
<ScrollArea className="w-full h-full pb-12 pt-8">
|
||||
<SidebarHeader isOpen={isOpen} />
|
||||
<div className="grid gap-2 mb-4">
|
||||
<SidebarItem
|
||||
@@ -194,28 +186,30 @@ export const Sidebar = () => {
|
||||
|
||||
<Separator />
|
||||
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<div className="px-1">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
id="preferences-button"
|
||||
className={`w-full ${
|
||||
isOpen ? "justify-start" : "justify-center"
|
||||
}`}
|
||||
data-tooltip-id="global-tooltip"
|
||||
data-tooltip-content={t("sidebar.preferences")}
|
||||
data-tooltip-place="right"
|
||||
>
|
||||
<SettingsIcon className="size-4" />
|
||||
{isOpen && (
|
||||
<span className="ml-2"> {t("sidebar.preferences")} </span>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogTrigger>
|
||||
<div className="px-1 non-draggable-region">
|
||||
<Button
|
||||
size="sm"
|
||||
variant={displayPreferences ? "default" : "ghost"}
|
||||
id="preferences-button"
|
||||
className={`w-full ${
|
||||
isOpen ? "justify-start" : "justify-center"
|
||||
}`}
|
||||
data-tooltip-id="global-tooltip"
|
||||
data-tooltip-content={t("sidebar.preferences")}
|
||||
data-tooltip-place="right"
|
||||
onClick={() => setDisplayPreferences(true)}
|
||||
>
|
||||
<SettingsIcon className="size-4" />
|
||||
{isOpen && (
|
||||
<span className="ml-2"> {t("sidebar.preferences")} </span>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Dialog
|
||||
open={displayPreferences}
|
||||
onOpenChange={setDisplayPreferences}
|
||||
>
|
||||
<DialogContent
|
||||
aria-describedby={undefined}
|
||||
className="max-w-screen-md xl:max-w-screen-lg h-5/6 p-0"
|
||||
@@ -226,82 +220,6 @@ export const Sidebar = () => {
|
||||
<Preferences />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<div className="px-1">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className={`w-full ${
|
||||
isOpen ? "justify-start" : "justify-center"
|
||||
}`}
|
||||
>
|
||||
<HelpCircleIcon className="size-4" />
|
||||
{isOpen && (
|
||||
<>
|
||||
<span className="ml-2"> {t("sidebar.help")} </span>
|
||||
<ChevronRightIcon className="w-4 h-4 ml-auto" />
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent
|
||||
className="w-[--radix-dropdown-menu-trigger-width]"
|
||||
align="start"
|
||||
side="top"
|
||||
>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
EnjoyApp.shell.openExternal(
|
||||
"https://1000h.org/enjoy-app/"
|
||||
)
|
||||
}
|
||||
className="flex justify-between space-x-4"
|
||||
>
|
||||
<span className="min-w-fit">{t("userGuide")}</span>
|
||||
<ExternalLinkIcon className="size-4" />
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
{t("feedback")}
|
||||
</DropdownMenuSubTrigger>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
EnjoyApp.shell.openExternal(
|
||||
"https://mixin.one/codes/f6ff96b8-54fb-4ad8-a6d4-5a5bdb1df13e"
|
||||
)
|
||||
}
|
||||
className="flex justify-between space-x-4"
|
||||
>
|
||||
<span>Mixin</span>
|
||||
<ExternalLinkIcon className="size-4" />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
EnjoyApp.shell.openExternal(
|
||||
"https://github.com/zuodaotech/everyone-can-use-english/discussions"
|
||||
)
|
||||
}
|
||||
className="flex justify-between space-x-4"
|
||||
>
|
||||
<span>Github</span>
|
||||
<ExternalLinkIcon className="size-4" />
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
</DropdownMenuGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
@@ -309,7 +227,9 @@ export const Sidebar = () => {
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className={`w-full ${isOpen ? "justify-start" : "justify-center"}`}
|
||||
className={`w-full non-draggable-region ${
|
||||
isOpen ? "justify-start" : "justify-center"
|
||||
}`}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
{isOpen ? (
|
||||
@@ -343,7 +263,7 @@ const SidebarItem = (props: {
|
||||
data-tooltip-content={tooltip}
|
||||
data-tooltip-place="right"
|
||||
data-testid={testid}
|
||||
className="block px-1"
|
||||
className="block px-1 non-draggable-region"
|
||||
>
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -362,8 +282,12 @@ const SidebarHeader = (props: { isOpen: boolean }) => {
|
||||
const { user, logout } = useContext(AppSettingsProviderContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="py-3 px-1 sticky top-0 bg-muted z-10">
|
||||
<div className="py-3 px-1 sticky top-0 bg-muted z-10 non-draggable-region">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
|
||||
@@ -8,7 +8,6 @@ import { DbProviderContext } from "@renderer/context";
|
||||
import { UserSettingKeyEnum } from "@/types/enums";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogTrigger,
|
||||
AlertDialogContent,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
@@ -16,10 +15,10 @@ import {
|
||||
AlertDialogFooter,
|
||||
AlertDialogCancel,
|
||||
AlertDialogAction,
|
||||
Button,
|
||||
} from "@renderer/components/ui";
|
||||
import { t } from "i18next";
|
||||
import { redirect } from "react-router-dom";
|
||||
import { Preferences } from "@renderer/components";
|
||||
|
||||
type AppSettingsProviderState = {
|
||||
webApi: Client;
|
||||
@@ -50,6 +49,8 @@ type AppSettingsProviderState = {
|
||||
setRecorderConfig?: (config: RecorderConfigType) => Promise<void>;
|
||||
// remote config
|
||||
ipaMappings?: { [key: string]: string };
|
||||
displayPreferences?: boolean;
|
||||
setDisplayPreferences?: (display: boolean) => void;
|
||||
};
|
||||
|
||||
const EnjoyApp = window.__ENJOY_APP__;
|
||||
@@ -87,6 +88,7 @@ export const AppSettingsProvider = ({
|
||||
IPA_MAPPINGS
|
||||
);
|
||||
const [loggingOut, setLoggingOut] = useState<boolean>(false);
|
||||
const [displayPreferences, setDisplayPreferences] = useState<boolean>(false);
|
||||
|
||||
const db = useContext(DbProviderContext);
|
||||
|
||||
@@ -344,12 +346,14 @@ export const AppSettingsProvider = ({
|
||||
setProxy: setProxyConfigHandler,
|
||||
vocabularyConfig,
|
||||
setVocabularyConfig: setVocabularyConfigHandler,
|
||||
initialized: Boolean(db.state === "connected" && libraryPath),
|
||||
initialized: Boolean(user && db.state === "connected" && libraryPath),
|
||||
ahoy,
|
||||
cable,
|
||||
recorderConfig,
|
||||
setRecorderConfig: setRecorderConfigHandler,
|
||||
ipaMappings,
|
||||
displayPreferences,
|
||||
setDisplayPreferences,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -25,7 +25,7 @@ export default () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="h-screen flex flex-col relative">
|
||||
<div className="h-[calc(100vh-2rem)] flex flex-col relative">
|
||||
<Breadcrumb className="px-4 pt-3 pb-2">
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
|
||||
@@ -231,9 +231,9 @@ export default () => {
|
||||
return (
|
||||
<div
|
||||
data-testid="conversation-page"
|
||||
className="h-screen px-4 py-6 lg:px-8 flex flex-col"
|
||||
className="h-[calc(100vh-2rem)] px-4 py-4 lg:px-8 flex flex-col"
|
||||
>
|
||||
<div className="h-[calc(100vh-3rem)] relative w-full max-w-screen-md mx-auto flex flex-col">
|
||||
<div className="h-[calc(100vh-5rem)] relative w-full max-w-screen-md mx-auto flex flex-col">
|
||||
<div className="flex items-center justify-center py-2 relative">
|
||||
<div className="cursor-pointer h-6 opacity-50 hover:opacity-100">
|
||||
<Link className="flex items-center" to="/conversations">
|
||||
|
||||
@@ -43,7 +43,7 @@ const DocumentComponent = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen flex flex-col relative">
|
||||
<div className="h-[calc(100vh-2rem)] flex flex-col relative">
|
||||
<Breadcrumb className="px-4 pt-3 pb-2">
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
|
||||
@@ -1,35 +1,65 @@
|
||||
import { t } from "i18next";
|
||||
import { useContext } from "react";
|
||||
import { useContext, useState } from "react";
|
||||
import { Button } from "@renderer/components/ui";
|
||||
import { Link } from "react-router-dom";
|
||||
import { LoginForm } from "@renderer/components";
|
||||
import { AppSettingsProviderContext } from "@renderer/context";
|
||||
import { Link, Navigate } from "react-router-dom";
|
||||
import { DbState, LoginForm } from "@renderer/components";
|
||||
import {
|
||||
AppSettingsProviderContext,
|
||||
DbProviderContext,
|
||||
} from "@renderer/context";
|
||||
|
||||
export default () => {
|
||||
const { initialized, EnjoyApp } = useContext(AppSettingsProviderContext);
|
||||
const { initialized, EnjoyApp, user } = useContext(
|
||||
AppSettingsProviderContext
|
||||
);
|
||||
const [started, setStarted] = useState(false);
|
||||
const db = useContext(DbProviderContext);
|
||||
|
||||
if (initialized) {
|
||||
return <Navigate to="/" replace />;
|
||||
}
|
||||
|
||||
if (user && db.state === "error") {
|
||||
return (
|
||||
<div
|
||||
className="flex justify-center items-center h-full"
|
||||
date-testid="layout-db-error"
|
||||
>
|
||||
<DbState />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!started) {
|
||||
return (
|
||||
<div
|
||||
className="flex justify-center items-center h-full"
|
||||
date-testid="layout-onboarding"
|
||||
>
|
||||
<div className="text-center">
|
||||
<div className="text-lg mb-6">
|
||||
{t("welcomeTo")} <span className="font-semibold">Enjoy App</span>
|
||||
</div>
|
||||
|
||||
<div className="">
|
||||
<Button size="lg" onClick={() => setStarted(true)}>
|
||||
{t("startToUse")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen w-full px-4 py-6 lg:px-8 flex flex-col">
|
||||
<div className="w-full h-full px-4 py-6 lg:px-8 flex flex-col gap-8">
|
||||
<div className="text-center">
|
||||
<div className="text-lg font-mono py-6">{t("login")}</div>
|
||||
<div className="text-lg font-mono py-4">{t("login")}</div>
|
||||
<div className="text-sm opacity-70">{t("loginBeforeYouStart")}</div>
|
||||
</div>
|
||||
<div className="flex-1 flex justify-center items-center">
|
||||
<div className="flex-1 flex justify-center">
|
||||
<LoginForm />
|
||||
</div>
|
||||
<div className="mt-auto">
|
||||
<div className="flex mb-4 justify-end space-x-4">
|
||||
{initialized ? (
|
||||
<Link data-testid="start-to-use-button" to="/" replace>
|
||||
<Button className="w-24">{t("startToUse")}</Button>
|
||||
</Link>
|
||||
) : (
|
||||
<Button className="w-24" onClick={() => EnjoyApp.app.reload()}>
|
||||
{t("reload")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
19
enjoy/src/renderer/pages/protected-page.tsx
Normal file
19
enjoy/src/renderer/pages/protected-page.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Navigate } from "react-router-dom";
|
||||
import { AppSettingsProviderContext } from "../context";
|
||||
import { useContext } from "react";
|
||||
|
||||
export const ProtectedPage = ({
|
||||
children,
|
||||
redirectPath = "/landing",
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
redirectPath?: string;
|
||||
}) => {
|
||||
const { initialized } = useContext(AppSettingsProviderContext);
|
||||
|
||||
if (!initialized) {
|
||||
return <Navigate to={redirectPath} replace />;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
@@ -14,6 +14,7 @@ import { t } from "i18next";
|
||||
import nlp from "compromise";
|
||||
import paragraphs from "compromise-paragraphs";
|
||||
import { useDebounce } from "@uidotdev/usehooks";
|
||||
import { type IpcRendererEvent } from "electron/renderer";
|
||||
nlp.plugin(paragraphs);
|
||||
|
||||
export default () => {
|
||||
@@ -154,6 +155,15 @@ export default () => {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const onWindowChange = (
|
||||
event: IpcRendererEvent,
|
||||
state: { event: string }
|
||||
) => {
|
||||
if (state.event === "resize") {
|
||||
setWebviewRect(containerRef.current.getBoundingClientRect());
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef?.current) return;
|
||||
if (!url) return;
|
||||
@@ -172,12 +182,10 @@ export default () => {
|
||||
if (!containerRef?.current) return;
|
||||
|
||||
setWebviewRect(containerRef.current.getBoundingClientRect());
|
||||
EnjoyApp.window.onResize(() => {
|
||||
setWebviewRect(containerRef.current.getBoundingClientRect());
|
||||
});
|
||||
EnjoyApp.window.onChange((_event, state) => onWindowChange(_event, state));
|
||||
|
||||
return () => {
|
||||
EnjoyApp.window.removeListeners();
|
||||
EnjoyApp.window.removeListener(onWindowChange);
|
||||
};
|
||||
}, [containerRef?.current]);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ export default () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="h-screen flex flex-col relative">
|
||||
<div className="h-[calc(100vh-2rem)] flex flex-col relative">
|
||||
<Breadcrumb className="px-4 pt-3 pb-2">
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
|
||||
@@ -25,6 +25,7 @@ import Courses from "./pages/courses/index";
|
||||
import Course from "./pages/courses/show";
|
||||
import Chapter from "./pages/courses/chapter";
|
||||
import Chats from "./pages/chats";
|
||||
import { ProtectedPage } from "./pages/protected-page";
|
||||
|
||||
export default createHashRouter([
|
||||
{
|
||||
@@ -32,96 +33,191 @@ export default createHashRouter([
|
||||
element: <Layout />,
|
||||
errorElement: <ErrorPage />,
|
||||
children: [
|
||||
{ index: true, element: <Home /> },
|
||||
{ path: "/landing", element: <Landing /> },
|
||||
{
|
||||
index: true,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Home />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/chats",
|
||||
element: <Chats />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Chats />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/courses",
|
||||
element: <Courses />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Courses />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/courses/:id",
|
||||
element: <Course />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Course />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/courses/:id/chapters/:sequence",
|
||||
element: <Chapter />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Chapter />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/community",
|
||||
element: <Community />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Community />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/users/:id",
|
||||
element: <User />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<User />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/profile",
|
||||
element: <Profile />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Profile />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/conversations",
|
||||
element: <Conversations />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Conversations />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/conversations/:id",
|
||||
element: <Conversation />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Conversation />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/pronunciation_assessments",
|
||||
element: <PronunciationAssessmentsIndex />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<PronunciationAssessmentsIndex />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/pronunciation_assessments/new",
|
||||
element: <PronunciationAssessmentsNew />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<PronunciationAssessmentsNew />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/vocabulary",
|
||||
element: <Vocabulary />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Vocabulary />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/audios",
|
||||
element: <Audios />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Audios />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/audios/:id",
|
||||
element: <Audio />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Audio />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/videos",
|
||||
element: <Videos />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Videos />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/videos/:id",
|
||||
element: <Video />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Video />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/documents",
|
||||
element: <Documents />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Documents />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/documents/:id",
|
||||
element: <Document />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Document />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/stories",
|
||||
element: <Stories />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Stories />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/stories/:id",
|
||||
element: <Story />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Story />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/stories/preview/:uri",
|
||||
element: <StoryPreview />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<StoryPreview />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "/notes",
|
||||
element: <Notes />,
|
||||
element: (
|
||||
<ProtectedPage>
|
||||
<Notes />
|
||||
</ProtectedPage>
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{ path: "/landing", element: <Landing /> },
|
||||
]);
|
||||
|
||||
17
enjoy/src/types/enjoy-app.d.ts
vendored
17
enjoy/src/types/enjoy-app.d.ts
vendored
@@ -17,8 +17,21 @@ type EnjoyAppType = {
|
||||
version: string;
|
||||
};
|
||||
window: {
|
||||
onResize: (callback: (event, bounds: any) => void) => void;
|
||||
removeListeners: () => void;
|
||||
onChange: (
|
||||
callback: (event, state: { event: string; state: any }) => void
|
||||
) => void;
|
||||
toggleMaximized: () => Promise<void>;
|
||||
isMaximized: () => Promise<boolean>;
|
||||
maximize: () => Promise<void>;
|
||||
unmaximize: () => Promise<void>;
|
||||
fullscreen: () => Promise<void>;
|
||||
unfullscreen: () => Promise<void>;
|
||||
minimize: () => Promise<void>;
|
||||
close: () => Promise<void>;
|
||||
removeListener: (
|
||||
listener: (event: IpcRendererEvent, ...args: any[]) => void
|
||||
) => void;
|
||||
removeAllListeners: () => void;
|
||||
};
|
||||
system: {
|
||||
preferences: {
|
||||
|
||||
Reference in New Issue
Block a user