diff --git a/packages/app/.gitignore b/packages/app/.gitignore index d54f00b3..8521d6b7 100644 --- a/packages/app/.gitignore +++ b/packages/app/.gitignore @@ -5,4 +5,5 @@ dist-ssr *.local .vscode yarn-error.log -ios/**/** \ No newline at end of file +ios/**/** +out/ \ No newline at end of file diff --git a/packages/app/config/index.js b/packages/app/config/index.js index 997ea298..22e20acc 100644 --- a/packages/app/config/index.js +++ b/packages/app/config/index.js @@ -10,6 +10,12 @@ const envOrigins = { contentApi: `http://${window.location.hostname}:3050`, streamingApi: `http://${window.location.hostname}:3002`, }, + "production": { + mainApi: "http://api.comty.pw", + authApi: "http://auth.comty.pw", + contentApi: "http://content.comty.pw", + streamingApi: "http://streaming.comty.pw", + }, "indev": { mainApi: "https://indev_api.comty.pw", authApi: `http://indev_auth.comty.pw`, diff --git a/packages/app/electron/main/configs/config.js b/packages/app/electron/main/configs/config.js new file mode 100644 index 00000000..2dbfdd50 --- /dev/null +++ b/packages/app/electron/main/configs/config.js @@ -0,0 +1,3 @@ +const Config = require('electron-config') + +module.exports = new Config({ name: 'config' }); \ No newline at end of file diff --git a/packages/app/electron/main/icon.png b/packages/app/electron/main/icon.png new file mode 100644 index 00000000..4a3d53b5 Binary files /dev/null and b/packages/app/electron/main/icon.png differ diff --git a/packages/app/electron/main/index.js b/packages/app/electron/main/index.js new file mode 100644 index 00000000..0fe2d761 --- /dev/null +++ b/packages/app/electron/main/index.js @@ -0,0 +1,254 @@ +const path = require("path") +const { + app, + BrowserWindow, + ipcMain, + Tray, + Menu, + MenuItem, + dialog, + shell, + screen, + BrowserView, + systemPreferences, + Notification, + globalShortcut +} = require("electron") + +const log = require("electron-log") +const is = require("electron-is") +const waitOn = require("wait-on") + +const srcPath = path.join(process.cwd()) +const distPath = path.join(process.cwd(), "dist") +const devMode = is.dev() + +const packagejson = require(path.join(srcPath, "package.json")) + +let app_path = devMode ? "http://localhost:8000" : `file://${path.join(distPath, "index.html")}` + +let mainWindow = null +let devtools = null +let tray = null +let watcher = null + +// This gets rid of this: https://github.com/electron/electron/issues/13186 +process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = true +// app.commandLine.appendSwitch("disable-web-security") +//app.commandLine.appendSwitch("disable-gpu-vsync=gpu") +app.commandLine.appendSwitch("disable-features", "OutOfBlinkCors") + +const gotTheLock = app.requestSingleInstanceLock() +const notifySupport = Notification.isSupported() + +// Prevent multiple instances +if (!gotTheLock) { + app.quit() +} + +function relaunchApp() { + mainWindow.close() + app.relaunch() +} + +function resumeApp() { + if (mainWindow) { + mainWindow.show() + mainWindow.focus() + } else { + createWindow() + } +} + +function notify(params) { + if (!notifySupport || !params) return false + let options = { + title: "", + body: "", + icon: null, + timeoutType: "default" + } + + const keys = Object.keys(params) + const values = Object.values(params) + + for (let i = 0; i < keys.length; i++) { + options[keys[i]] = values[i] + } + + new Notification(options).show() +} + +function createWindow() { + mainWindow = new BrowserWindow({ + title: packagejson.title, + icon: path.join(__dirname, "./icon.png"), + width: 1100, + height: 700, + minWidth: 1256, + minHeight: 755, + show: true, + frame: false, + transparent: false, + hasShadow: true, + //webgl: true, + visualEffectState: "followWindow", + backgroundColor: "#00ffffff", + webPreferences: { + preload: path.join(__dirname, "../preload"), + //enableRemoteModule: true, + enableBlinkFeatures: true, + experimentalFeatures: true, + nodeIntegration: true, + // Disable in dev since I think hot reload is messing with it + webSecurity: !is.dev() + } + }) + + if (devMode) { + app.commandLine.appendSwitch("remote-debugging-port", "9222") + + globalShortcut.register("CommandOrControl+R", () => { + mainWindow.reload() + }) + + globalShortcut.register("F5", () => { + mainWindow.reload() + }) + + globalShortcut.register("CommandOrControl+Shift+I", () => { + mainWindow.webContents.toggleDevTools() + }) + + devtools = new BrowserWindow() + + mainWindow.webContents.setDevToolsWebContents(devtools.webContents) + + mainWindow.webContents.openDevTools({ + mode: "detach" + }) + } + + mainWindow.webContents.session.webRequest.onHeadersReceived( + { + urls: ["http://*/*", "https://*/*"] + }, + (details, callback) => { + delete details.responseHeaders["Access-Control-Allow-Origin"] + delete details.responseHeaders["access-control-allow-origin"] + if (details.url.includes("www.google-analytics.com")) { + details.responseHeaders["Access-Control-Allow-Origin"] = [app_path] + } else { + details.responseHeaders["Access-Control-Allow-Origin"] = ["*"] + } + callback({ + cancel: false, + responseHeaders: details.responseHeaders + }) + } + ) + + // load the app + mainWindow.loadURL(app_path) + mainWindow.focus() + + // const handleRedirect = (e, url) => { + // if (url !== mainWindow.webContents.getURL()) { + // e.preventDefault() + // shell.openExternal(url) + // } + // } + + // mainWindow.webContents.on("will-navigate", handleRedirect) + // mainWindow.webContents.on("new-window", handleRedirect) +} + +app.on("ready", async () => { + if (!devMode) { + return createWindow() + } + + loadWindow = new BrowserWindow({ + width: 700, + height: 500, + frame: false, + resizable: false, + center: true, + transparent: true, + backgroundColor: "#00000000", + }) + + console.log("Starting on dev mode....") + notify({ title: "Starting development server..." }) + + loadWindow.loadURL(`file://${__dirname}/statics/loading_dev.html`) + loadWindow.focus() + + await waitOn({ resources: [app_path] }, (err) => { + if (err) { + return console.error(err) + } + }) + + loadWindow.close() + + createWindow() +}) + +app.on("window-all-closed", () => { + mainWindow = null +}) + +app.on("before-quit", async () => { + mainWindow.removeAllListeners("close") + mainWindow = null +}) + +ipcMain.handle("update-progress-bar", (event, p) => { + mainWindow.setProgressBar(p) +}) + +ipcMain.handle("hide-window", () => { + if (mainWindow) { + mainWindow.hide() + } +}) + +ipcMain.handle("show-window", () => { + if (mainWindow) { + mainWindow.show() + mainWindow.focus() + } +}) + +ipcMain.handle("min-max-window", () => { + if (mainWindow.isMaximized()) { + mainWindow.unmaximize() + } else if (mainWindow.maximizable) { + mainWindow.maximize() + } +}) + +ipcMain.handle("getSystemPreferences", () => { + return systemPreferences +}) + +ipcMain.handle("app.minimize", () => { + mainWindow.minimize() +}) + +ipcMain.handle("app.close", () => { + app.quit() +}) + +ipcMain.handle("open-devtools", () => { + mainWindow.webContents.openDevTools({ mode: "undocked" }) +}) + +ipcMain.handle("appRelaunch", () => { + relaunchApp() +}) + +ipcMain.handle("inspectElement", (event, payload) => { + mainWindow.inspectElement(payload.x, payload.y) +}) \ No newline at end of file diff --git a/packages/app/electron/main/statics/loading.css b/packages/app/electron/main/statics/loading.css new file mode 100644 index 00000000..8b3d3063 --- /dev/null +++ b/packages/app/electron/main/statics/loading.css @@ -0,0 +1,112 @@ + body { + background-color: transparent; + border-radius: 12px; + font-family: 'Alata', sans-serif; + width: 100vw; + height: 100vh; + overflow: hidden; + user-select: none; + -webkit-app-region: drag; + } + + .wrapper { + position: absolute; + top: 0; + right: 0; + + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + background-color: #efefef; + color: #2d2d2d; + border-radius: 12px; + text-align: center; + + width: 100%; + height: 100%; + } + + .bouncy-logo{ + width: 100%; + height: auto; + margin: auto; + } + + .bouncy-logo .ball { + height: auto; + width: 100%; + transform: translate(-10px, 0); + } + + .bouncy-logo .ball svg { + width: 200px; + height: 200px; + margin: auto; + + -webkit-animation-direction: alternate; + -webkit-animation-duration: 1s; + -webkit-animation-name: my-bounce; + -webkit-animation-iteration-count: infinite; + animation-direction: alternate; + animation-duration: 1s; + animation-name: my-bounce; + animation-iteration-count: infinite; + } + + .shadow{ + width: 100%; + height: auto; + } + + .bouncy-logo .ball-shadow { + margin: auto; + background: radial-gradient(50% 50%, rgba(204, 204, 204, 0.45) 0%, transparent 100%); + height: 50px; + width: 200px; + -webkit-animation-direction: alternate; + -webkit-animation-duration: 1s; + -webkit-animation-name: my-grow; + -webkit-animation-iteration-count: infinite; + animation-direction: alternate; + animation-duration: 1s; + animation-name: my-grow; + animation-iteration-count: infinite; + } + + @-webkit-keyframes my-grow { + from { + width: 200px; + height: 50px; + } + to { + width: 150px; + height: 10px; + } + } + @keyframes my-grow { + from { + width: 200px; + height: 50px; + } + to { + width: 150px; + height: 10px; + } + } + @-webkit-keyframes my-bounce { + from { + top: 0; + } + to { + top: 10%; + } + } + @keyframes my-bounce { + from { + top: 0; + } + to { + top: 10%; + } + } \ No newline at end of file diff --git a/packages/app/electron/main/statics/loading.html b/packages/app/electron/main/statics/loading.html new file mode 100644 index 00000000..039634a8 --- /dev/null +++ b/packages/app/electron/main/statics/loading.html @@ -0,0 +1,23 @@ + + + + +
+ +
+ + \ No newline at end of file diff --git a/packages/app/electron/main/statics/loading_dev.html b/packages/app/electron/main/statics/loading_dev.html new file mode 100644 index 00000000..d45f8e69 --- /dev/null +++ b/packages/app/electron/main/statics/loading_dev.html @@ -0,0 +1,24 @@ + + + + +
+ +

Starting the development server...

+
+ + \ No newline at end of file diff --git a/packages/app/electron/preload/index.js b/packages/app/electron/preload/index.js new file mode 100644 index 00000000..1d2584c1 --- /dev/null +++ b/packages/app/electron/preload/index.js @@ -0,0 +1,8 @@ +const { contextBridge, ipcRenderer } = require("electron") + +contextBridge.exposeInMainWorld("electron", { + ipcRenderer, + isDev: () => { + return process.env.NODE_ENV === "development" + } +}) \ No newline at end of file diff --git a/packages/app/package.json b/packages/app/package.json index bafb987a..de643354 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -2,15 +2,20 @@ "name": "comty", "version": "0.20.0", "license": "MIT", + "main": "electron/main", + "author": "RageStudio", "scripts": { + "start": "electron-forge start", + "build": "vite build", "dev": "vite", - "dev:ssr": "vite-ssr", - "build": "yarn build:dist", - "build:dist": "cross-env NODE_ENV=production vite build", - "build:preview": "cross-env NODE_ENV=preview vite build && yarn sync", - "build:ssr": "cross-env NODE_ENV=production vite-ssr build && ./scripts/move-dist.sh", + "dev:electron": "concurrently -k \"yarn dev\" \"yarn electron:dev\"", + "electron:dev": "cross-env IS_DEV=true electron-forge start", + "electron:build": "electron-forge make", + "electron:package": "electron-forge package", "docker-compose:update_run": "docker-compose down && git pull && yarn build && docker-compose up -d --build", - "preview": "vite preview" + "preview": "vite preview", + "package": "electron-forge package", + "make": "electron-forge make" }, "peerDependencies": { "react": "^16.8.6" @@ -27,10 +32,14 @@ "@emotion/css": "11.0.0", "@foxify/events": "2.0.1", "@loadable/component": "5.15.2", + "@vitejs/plugin-react": "^2.1.0", "antd": "4.23.2", "antd-mobile": "^5.0.0-rc.17", "chart.js": "3.7.0", "classnames": "2.3.1", + "electron-is": "^3.0.0", + "electron-log": "^4.4.8", + "electron-squirrel-startup": "^1.0.0", "evite": "0.12.3", "faye": "1.4.0", "feather-reactjs": "2.0.13", @@ -72,9 +81,14 @@ "rxjs": "^7.5.5", "store": "^2.0.12", "styled-components": "^5.3.3", - "vite-ssr": "0.15.0" + "wait-on": "^6.0.1" }, "devDependencies": { + "@electron-forge/cli": "^6.0.0-beta.66", + "@electron-forge/maker-deb": "^6.0.0-beta.66", + "@electron-forge/maker-rpm": "^6.0.0-beta.66", + "@electron-forge/maker-squirrel": "^6.0.0-beta.66", + "@electron-forge/maker-zip": "^6.0.0-beta.66", "@types/jest": "^26.0.24", "@types/node": "^16.4.10", "@types/react": "^17.0.15", @@ -82,12 +96,40 @@ "@types/react-router-config": "^5.0.3", "@types/react-router-dom": "^5.1.8", "@typescript-eslint/eslint-plugin": "^4.29.0", - "@vitejs/plugin-react-refresh": "^1.3.6", + "concurrently": "^7.4.0", "corenode": "0.28.26", "cors": "2.8.5", "cross-env": "^7.0.3", + "electron": "^21.0.1", "express": "^4.17.1", "typescript": "^4.3.5", - "vite": "2.9.9" + "vite": "3.1.4" + }, + "config": { + "forge": { + "packagerConfig": {}, + "makers": [ + { + "name": "@electron-forge/maker-squirrel", + "config": { + "name": "comty" + } + }, + { + "name": "@electron-forge/maker-zip", + "platforms": [ + "darwin" + ] + }, + { + "name": "@electron-forge/maker-deb", + "config": {} + }, + { + "name": "@electron-forge/maker-rpm", + "config": {} + } + ] + } } } diff --git a/packages/app/src/App.jsx b/packages/app/src/App.jsx index 985efe01..d6fdf517 100644 --- a/packages/app/src/App.jsx +++ b/packages/app/src/App.jsx @@ -43,6 +43,8 @@ Promise.tasked = function (promises) { } import React from "react" +import ReactDOM from "react-dom" + import { EviteRuntime } from "evite" import { Helmet } from "react-helmet" import * as antd from "antd" @@ -71,8 +73,28 @@ class App extends React.Component { user: null, } - static initialize() { + static async initialize() { window.app.version = config.package.version + + // check if electron library is available + if (typeof window.electron !== "undefined") { + window.isElectron = true + } + + // if is electron app, append frame to body as first child + if (window.isElectron) { + const frame = document.createElement("div") + const systemBarComponent = await import("components/layout/systemBar") + + frame.id = "systemBar" + + ReactDOM.render(, frame) + + document.body.insertBefore(frame, document.body.firstChild) + + // append var to #root + document.getElementById("root").classList.add("electron") + } } static publicEvents = { @@ -82,6 +104,16 @@ class App extends React.Component { } eventsHandlers = { + "app.close": () => { + if (window.isElectron) { + window.electron.ipcRenderer.invoke("app.close") + } + }, + "app.minimize": () => { + if (window.isElectron) { + window.electron.ipcRenderer.invoke("app.minimize") + } + }, "app.openCreator": (...args) => { return App.publicMethods.openCreator(...args) }, diff --git a/packages/app/src/components/layout/sidebar/index.less b/packages/app/src/components/layout/sidebar/index.less index 30cad8f1..f018af21 100644 --- a/packages/app/src/components/layout/sidebar/index.less +++ b/packages/app/src/components/layout/sidebar/index.less @@ -2,26 +2,26 @@ // SIDEBAR .ant-layout-sider { - z-index : 50; + z-index: 50; - background : var(--sidebar-background-color) !important; + background: var(--sidebar-background-color) !important; background-color: var(--sidebar-background-color) !important; border-radius: 0 @app_sidebar_borderRadius @app_sidebar_borderRadius 0; - overflow : hidden; - border : 1px solid var(--sidebar-background-color); + overflow: hidden; + border: 1px solid var(--sidebar-background-color); transition: all 150ms ease-in-out; &.hidden { - flex : 0 !important; - min-width : 0 !important; + flex: 0 !important; + min-width: 0 !important; background-color: transparent !important; - width : 0 !important; + width: 0 !important; } &.elevated { - box-shadow : 0 0 5px 4px rgba(0, 0, 0, 0.1) !important; + box-shadow: 0 0 5px 4px rgba(0, 0, 0, 0.1) !important; } } @@ -42,36 +42,36 @@ .ant-menu, .ant-menu ul { - background : transparent !important; + background: transparent !important; background-color: transparent !important; border-right: 0 !important; } .sidebar .ant-layout-sider-children { - margin-top : 15px !important; + margin-top: 15px !important; margin-bottom: 15px !important; - background : transparent !important; + background: transparent !important; background-color: transparent !important; - user-select : none; + user-select: none; --webkit-user-select: none; - transition : all 150ms ease-in-out; - height : 100%; - display : flex; + transition: all 150ms ease-in-out; + height: 100%; + display: flex; flex-direction: column; &.edit_mode .ant-layout-sider-children { - background : transparent !important; + background: transparent !important; background-color: transparent !important; margin-top: 15px !important; .app_sidebar_menu_wrapper { - opacity : 0; - height : 0; + opacity: 0; + height: 0; overflow: hidden; } } @@ -81,36 +81,36 @@ transition: all 450ms ease-in-out; height: 100%; - width : 100%; + width: 100%; } .app_sidebar_header { - background : transparent !important; + background: transparent !important; background-color: transparent !important; - user-select : none; + user-select: none; --webkit-user-select: none; - display : flex; + display: flex; flex-direction: column; - height : 15%; - margin-top : 5%; - margin-bottom : 5%; + height: 15%; + margin-top: 5%; + margin-bottom: 5%; } .app_sidebar_header_logo { - user-select : none; + user-select: none; --webkit-user-select: none; - display : flex; - align-items : center; + display: flex; + align-items: center; justify-content: center; img { - user-select : none; + user-select: none; --webkit-user-select: none; - width : 80%; + width: 80%; max-height: 80px; } @@ -122,39 +122,39 @@ } .app_sidebar_menu { - background : transparent !important; + background: transparent !important; background-color: transparent !important; - height : 65%; - overflow : overlay; + height: 65%; + overflow: overlay; overflow-x: hidden; } .app_sidebar_bottom { - position : absolute; - bottom : 0; + position: absolute; + bottom: 0; padding-bottom: 30px; - z-index : 50; - left : 0; + z-index: 50; + left: 0; - background : transparent !important; - background-color : transparent !important; - backdrop-filter : blur(10px); + background: transparent !important; + background-color: transparent !important; + backdrop-filter: blur(10px); --webkit-backdrop-filter: blur(10px); - width : 100%; + width: 100%; height: fit-content; .ant-menu, ul { - background : transparent !important; + background: transparent !important; background-color: transparent !important; } } .user_avatar { - display : flex; - align-items : center; + display: flex; + align-items: center; justify-content: center; - padding : 0 !important; + padding: 0 !important; } \ No newline at end of file diff --git a/packages/app/src/components/layout/systemBar/index.jsx b/packages/app/src/components/layout/systemBar/index.jsx new file mode 100644 index 00000000..e3e38a33 --- /dev/null +++ b/packages/app/src/components/layout/systemBar/index.jsx @@ -0,0 +1,25 @@ +import React from "react" +import { Icons } from "components/Icons" + +import "./index.less" + +export default (props) => { + const handleClose = () => { + app.eventBus.emit("app.close") + } + + const handleMinimize = () => { + app.eventBus.emit("app.minimize") + } + + return
+
+
+ +
+
+ +
+
+
+} \ No newline at end of file diff --git a/packages/app/src/components/layout/systemBar/index.less b/packages/app/src/components/layout/systemBar/index.less new file mode 100644 index 00000000..df7599af --- /dev/null +++ b/packages/app/src/components/layout/systemBar/index.less @@ -0,0 +1,58 @@ +.app_systemBar { + -webkit-app-region: drag; + + display: inline-flex; + flex-direction: row; + + align-items: center; + justify-content: flex-end; + + position: fixed; + z-index: 1000; + + background-color: transparent; + + top: 0; + left: 0; + right: 0; + + height: 30px; + width: 100%; + + //backdrop-filter: blur(5px); + + .icons { + display: inline-flex; + align-items: center; + + padding: 0 10px; + height: 100%; + + .icon { + -webkit-app-region: no-drag; + cursor: pointer; + + display: inline-flex; + align-items: center; + justify-content: center; + + + margin: 0 5px; + width: 20px; + height: 20px; + + color: #fff; + + border-radius: 10px; + + svg { + margin: 0 !important; + color: var(--text-color); + } + } + + :first-child { + margin-left: 5px; + } + } +} \ No newline at end of file diff --git a/packages/app/src/pages/home/index.less b/packages/app/src/pages/home/index.less index 83061f04..1234e453 100644 --- a/packages/app/src/pages/home/index.less +++ b/packages/app/src/pages/home/index.less @@ -1,6 +1,6 @@ .dashboard { display: grid; - grid-template-columns: 25% repeat(2, 1fr); + grid-template-columns: 10vw repeat(2, 1fr); grid-template-rows: 1fr; grid-column-gap: 20px; grid-row-gap: 0px; diff --git a/packages/app/src/pages/notifications/index.jsx b/packages/app/src/pages/notifications/index.jsx index d8551230..56b57f49 100644 --- a/packages/app/src/pages/notifications/index.jsx +++ b/packages/app/src/pages/notifications/index.jsx @@ -5,6 +5,6 @@ import "./index.less" // TODO: Implement this component export default (props) => { return
- +
} \ No newline at end of file diff --git a/packages/app/src/theme/index.less b/packages/app/src/theme/index.less index 01567292..ad8869b8 100644 --- a/packages/app/src/theme/index.less +++ b/packages/app/src/theme/index.less @@ -95,6 +95,16 @@ html { height: 100%; background-color: var(--layoutBackgroundColor) !important; + + &.electron { + .layout_page { + padding-top: 35px; + } + + .ant-layout-sider { + padding-top: 0px; + } + } } #nprogress { @@ -231,6 +241,10 @@ html { margin-bottom: 40px; } + svg { + color: var(--text-color) !important; + } + .splash_logo { width: 200px; height: 200px; diff --git a/packages/app/vite.config.js b/packages/app/vite.config.js index 8e82569d..176c6546 100644 --- a/packages/app/vite.config.js +++ b/packages/app/vite.config.js @@ -1,11 +1,14 @@ +import path from "path" import getConfig from "./.config.js" import { defineConfig } from "vite" -import reactRefresh from "@vitejs/plugin-react-refresh" +import react from "@vitejs/plugin-react" + +//import electron, { onstart } from "vite-plugin-electron" export default defineConfig({ plugins: [ - reactRefresh(), + react(), ], ...getConfig(), }) \ No newline at end of file