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,