diff --git a/enjoy/src/constants/index.ts b/enjoy/src/constants/index.ts index 5a551954..64919d40 100644 --- a/enjoy/src/constants/index.ts +++ b/enjoy/src/constants/index.ts @@ -33,6 +33,7 @@ export const AI_WORKER_ENDPOINT = "https://ai-worker.enjoy.bot"; export const WEB_API_URL = "https://enjoy.bot"; export const WS_URL = "wss://enjoy.bot"; +export const DISCUSS_URL = "https://discuss.enjoy.bot"; export const DOWNLOAD_URL = "https://1000h.org/enjoy-app/install.html"; diff --git a/enjoy/src/main/window.ts b/enjoy/src/main/window.ts index 9e012696..26021eff 100644 --- a/enjoy/src/main/window.ts +++ b/enjoy/src/main/window.ts @@ -16,7 +16,7 @@ import settings from "@main/settings"; import downloader from "@main/downloader"; import fs from "fs-extra"; import log from "@main/logger"; -import { REPO_URL, WS_URL } from "@/constants"; +import { DISCUSS_URL, REPO_URL, WEB_API_URL, WS_URL } from "@/constants"; import { AudibleProvider, TedProvider, YoutubeProvider } from "@main/providers"; import Ffmpeg from "@main/ffmpeg"; import { Waveform } from "./waveform"; @@ -238,6 +238,108 @@ main.init = async () => { view.webContents.loadURL(url); } ); + + ipcMain.handle( + "view-load-community", + ( + event, + bounds: { x: number; y: number; width: number; height: number }, + options?: { + navigatable?: boolean; + accessToken?: string; + url?: string; + ssoUrl?: string; + } + ) => { + const { + x = 0, + y = 0, + width = mainWindow.getBounds().width, + height = mainWindow.getBounds().height, + } = bounds; + const { navigatable = false, accessToken, url = `${DISCUSS_URL}/login`, ssoUrl = `${WEB_API_URL}/discourse/sso` } = options || {}; + + logger.debug("view-load-community", url); + const view = new WebContentsView(); + mainWindow.contentView.addChildView(view); + + view.setBounds({ + x: Math.round(x), + y: Math.round(y), + width: Math.round(width), + height: Math.round(height), + }); + + view.webContents.on("did-navigate", (_event, url) => { + event.sender.send("view-on-state", { + state: "did-navigate", + url, + }); + }); + view.webContents.on( + "did-fail-load", + (_event, _errorCode, errrorDescription, validatedURL) => { + event.sender.send("view-on-state", { + state: "did-fail-load", + error: errrorDescription, + url: validatedURL, + }); + (view.webContents as any).destroy(); + mainWindow.contentView.removeChildView(view); + } + ); + + view.webContents.on("will-redirect", (details) => { + const { url } = details; + event.sender.send("view-on-state", { + state: "will-redirect", + url, + }); + // Login via SSO + if (url.includes(ssoUrl)) { + details.preventDefault(); + // Auto login using access token + view.webContents.loadURL(url, { + extraHeaders: `Authorization: Bearer ${accessToken}\n`, + }); + logger.debug("loading", url, "accessToken:", accessToken); + } else { + logger.debug("will-redirect", url); + } + }); + + view.webContents.on("will-navigate", (details) => { + const { url } = details; + event.sender.send("view-on-state", { + state: "will-navigate", + url, + }); + + logger.debug("will-navigate", url); + + // Open in browser if not navigatable + if (!navigatable) { + logger.debug("prevent navigation", url); + details.preventDefault(); + shell.openExternal(url); + } + }); + view.webContents.loadURL(url); + } + ); + + ipcMain.handle("view-resize", (_event, bounds: { x: number; y: number; width: number; height: number }) => { + logger.debug("view-resize", bounds); + const view = mainWindow.contentView.children[0]; + if (!view) return; + + view.setBounds({ + x: Math.round(bounds.x), + y: Math.round(bounds.y), + width: Math.round(bounds.width), + height: Math.round(bounds.height), + }); + }); ipcMain.handle("view-remove", () => { logger.debug("view-remove"); diff --git a/enjoy/src/preload.ts b/enjoy/src/preload.ts index 4f3fe840..ddc9b601 100644 --- a/enjoy/src/preload.ts +++ b/enjoy/src/preload.ts @@ -186,6 +186,21 @@ contextBridge.exposeInMainWorld("__ENJOY_APP__", { scrape: (url: string) => { return ipcRenderer.invoke("view-scrape", url); }, + resize: (bounds: { + x: number; + y: number; + width: number; + height: number; + }) => { + return ipcRenderer.invoke("view-resize", bounds); + }, + loadCommunity: ( + url: string, + bounds: { x: number; y: number; width: number; height: number }, + options?: { navigatable?: boolean; accessToken?: string } + ) => { + return ipcRenderer.invoke("view-load-community", url, bounds, options); + }, onViewState: ( callback: ( event: IpcRendererEvent, diff --git a/enjoy/src/renderer/components/layouts/sidebar.tsx b/enjoy/src/renderer/components/layouts/sidebar.tsx index a88a3d09..292df186 100644 --- a/enjoy/src/renderer/components/layouts/sidebar.tsx +++ b/enjoy/src/renderer/components/layouts/sidebar.tsx @@ -123,6 +123,15 @@ export const Sidebar = (props: { isCollapsed={isCollapsed} /> + + { {t("sidebar.profile")} - navigate("/community")} - > - {t("sidebar.community")} - - setDisplayDepositDialog(true)} diff --git a/enjoy/src/renderer/pages/community.tsx b/enjoy/src/renderer/pages/community.tsx index 67806a1a..4d7ddd8a 100644 --- a/enjoy/src/renderer/pages/community.tsx +++ b/enjoy/src/renderer/pages/community.tsx @@ -1,34 +1,61 @@ -import { - Button, - Tabs, - TabsList, - TabsContent, - TabsTrigger, -} from "@renderer/components/ui"; -import { UsersRankings, Posts } from "@renderer/components"; -import { ChevronLeftIcon } from "lucide-react"; -import { useNavigate } from "react-router-dom"; -import { t } from "i18next"; +import { useContext, useEffect, useRef } from "react"; +import { AppSettingsProviderContext } from "@renderer/context"; +import debounce from "lodash/debounce"; +import { DISCUSS_URL, WEB_API_URL } from "@/constants"; export default () => { - const navigate = useNavigate(); + const containerRef = useRef(null); + const { EnjoyApp, user, webApi } = useContext(AppSettingsProviderContext); - return ( -
- - - {t("square")} - {t("rankings")} - + const loadCommunity = async () => { + let url = `${DISCUSS_URL}/login`; + let ssoUrl = `${WEB_API_URL}/discourse/sso`; - - - + try { + const { discussUrl, discussSsoUrl } = await webApi.config("discuss"); + if (discussUrl) { + url = discussUrl; + } + if (discussSsoUrl) { + ssoUrl = discussSsoUrl; + } + } catch (error) { + console.error(error); + } - - - - -
- ); + const { x, y, width, height } = + containerRef.current.getBoundingClientRect(); + EnjoyApp.view.loadCommunity( + { x, y, width, height }, + { navigatable: false, accessToken: user?.accessToken, url, ssoUrl } + ); + }; + + const resize = debounce(() => { + const { x, y, width, height } = + containerRef.current.getBoundingClientRect(); + EnjoyApp.view.resize({ x, y, width, height }); + }, 100); + + useEffect(() => { + if (!containerRef.current) return; + + loadCommunity(); + const observer = new ResizeObserver(() => { + resize(); + }); + observer.observe(containerRef.current); + + return () => { + observer.disconnect(); + }; + }, []); + + useEffect(() => { + return () => { + EnjoyApp.view.remove(); + }; + }, []); + + return
; }; diff --git a/enjoy/src/types/enjoy-app.d.ts b/enjoy/src/types/enjoy-app.d.ts index b3f91bd5..3f0d034c 100644 --- a/enjoy/src/types/enjoy-app.d.ts +++ b/enjoy/src/types/enjoy-app.d.ts @@ -91,6 +91,21 @@ type EnjoyAppType = { hide: () => Promise; remove: () => Promise; scrape: (url: string) => Promise; + loadCommunity: ( + bounds: { x: number; y: number; width: number; height: number }, + options?: { + navigatable?: boolean; + accessToken?: string; + url?: string; + ssoUrl?: string; + } + ) => Promise; + resize: (bounds: { + x: number; + y: number; + width: number; + height: number; + }) => Promise; onViewState: ( callback: ( event,