diff --git a/README.md b/README.md index b0ac0891..7f096e04 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,14 @@ Install node.js +Generate Electron Build for desktop +========================= +* $ npm run electron:package:linux +* $ npm run electron:package:win +* $ npm run electron:package:mac + +Note: +Build targeting windows requires wine to be installed Contributing Please see [.github/CONTRIBUTING.md ](.github/CONTRIBUTING.md ) ========================= diff --git a/electron.js b/electron.js new file mode 100644 index 00000000..6be52b77 --- /dev/null +++ b/electron.js @@ -0,0 +1,102 @@ + +// Modules to control application life and create native browser window +// Module to control the application lifecycle and the native browser window. +const { app, BrowserWindow, protocol } = require("electron"); +const path = require("path"); +const url = require("url"); + +// Create the native browser window. +function createWindow() { + const mainWindow = new BrowserWindow({ + width: 1300 , + height: 850, + minWidth: 500, + minHeight: 500, + autoHideMenuBar:false, + webPreferences: { + nodeIntegration: true, + contextIsolation: true, + devTools: true, + }, + }); + + // In production, set the initial browser path to the local bundle generated + // by the Create React App build process. + // In development, set it to localhost to allow live/hot-reloading. + const appURL = app.isPackaged + ? url.format({ + pathname: path.join(__dirname, "/dist/index.html"), + protocol: "file:", + slashes: true, + }) + : "http://localhost:3000"; + mainWindow.loadURL(appURL); + mainWindow.maximize() + + // Automatically open Chrome's DevTools in development mode. + if (!app.isPackaged) { + mainWindow.webContents.openDevTools(); + } +} + +// Setup a local proxy to adjust the paths of requested files when loading +// them from the local production bundle (e.g.: local fonts, etc...). +function setupLocalFilesNormalizerProxy() { + protocol.registerHttpProtocol( + "file", + (request, callback) => { + const url = request.url.substr(8); + callback({ path: path.normalize(`${__dirname}/${url}`) }); + }, + (error) => { + if (error) console.error("Failed to register protocol"); + } + ); +} + +// This method will be called when Electron has finished its initialization and +// is ready to create the browser windows. +// Some APIs can only be used after this event occurs. +app.whenReady().then(() => { + createWindow(); + setupLocalFilesNormalizerProxy(); + + app.on("activate", function () { + // On macOS it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } + }); +}); + +// Quit when all windows are closed, except on macOS. +// There, it's common for applications and their menu bar to stay active until +// the user quits explicitly with Cmd + Q. +app.on("window-all-closed", function () { + if (process.platform !== "darwin") { + app.quit(); + } +}); + +// If your app has no need to navigate or only needs to navigate to known pages, +// it is a good idea to limit navigation outright to that known scope, +// disallowing any other kinds of navigation. +const allowedNavigationDestinations = "https://my-electron-app.com"; +app.on("web-contents-created", (event, contents) => { + contents.on("will-navigate", (event, navigationUrl) => { + const parsedUrl = new URL(navigationUrl); + + if (!allowedNavigationDestinations.includes(parsedUrl.origin)) { + event.preventDefault(); + } + }); +}); + +// In this file you can include the rest of your app's specific main process +// code. You can also put them in separate files and require them here. + +/* + const mainWindow = new BrowserWindow({ + + })*/ \ No newline at end of file diff --git a/package.json b/package.json index e8d6adee..01f8a94c 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,68 @@ { "name": "jsketcher", "version": "0.1.0", + "main": "electron.js", + "private": true, "description": "JS.Sketcher is a parametric 2D and 3D CAD modeler written in pure javascript", + "build": { + "appId": "jsketcher", + "linux": { + "category": "Utility", + "icon": "icon.png", + "target": [ + "deb", + "appImage" + ] + }, + "deb": { + "depends": [ + "gconf2", + "gconf-service", + "libnotify4", + "libappindicator1", + "libxtst6", + "libnss3" + ] + }, + "win": { + "icon": "icon.png" + }, + "mac": { + "icon": "icon.icns", + "category": "public.app-category.utilities" + }, + "files": [ + "build/*", + "dev-guide", + "misc/*", + "modules/**/**/*", + "node_modules/**/**/*", + "public/*", + "test/**/**/*", + "web/**/**/*", + ".babelrc", + ".eslintrc.json", + ".gitignore", + "externals.d.ts", + "Gruntfile.js", + "cypress.json", + "package.json", + "package-lock.json", + "tsconfig.json", + "electron.js", + "dist/**/*", + "webpack.config.js" + ], + "extraMetadata": { + "main": "public/electron.js" + }, + "directories": { + "buildResources": "build" + } + }, "scripts": { "start": "node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js --config webpack.config.js --port 3000 --host 0.0.0.0", + "electron": "concurrently \"cross-env BROWSER=none yarn start\" \"wait-on http://localhost:3000 && electron .\"", "pack": "node ./node_modules/webpack/bin/webpack.js --config webpack.config.js --progress --profile --colors", "start-with-docs": "concurrently --kill-others 'npm start' './node_modules/grunt/bin/grunt docs-start'", "build": "grunt", @@ -12,7 +71,10 @@ "check-code": "./node_modules/typescript/bin/tsc --noEmit", "checks-passed-banner": "echo 'All conditions passed. Ready to go to the main branch. Good job!'", "before-main-branch-merge": "npm run lint && npm run check-code && npm run checks-passed-banner", - "cypress": "npx cypress open" + "cypress": "npx cypress open", + "electron:package:mac": "npm run build && electron-builder -m -c.extraMetadata.main=electron.js", + "electron:package:win": "npm run build && electron-builder -w -c.extraMetadata.main=electron.js", + "electron:package:linux": "npm run build && electron-builder -l -c.extraMetadata.main=electron.js" }, "repository": { "type": "git", @@ -27,7 +89,7 @@ "bugs": { "url": "https://github.com/xibyte/jsketcher/issues" }, - "homepage": "https://github.com/xibyte/jsketcher", + "homepage": ".", "devDependencies": { "@babel/cli": "^7.18.10", "@babel/core": "^7.18.10", @@ -49,11 +111,14 @@ "babel-loader": "^8.2.5", "babel-plugin-transform-decorators-legacy": "^1.3.5", "babel-polyfill": "^6.26.0", + "buffer": "^6.0.3", "concurrently": "^7.3.0", + "cross-env": "^7.0.3", "css-loader": "^3.4.2", "cypress": "^4.7.0", "cypress-wait-until": "^1.7.1", "del": "^6.0.0", + "electron": "^21.2.0", "eslint": "^8.22.0", "eslint-plugin-import": "^2.26.0", "file-loader": "^6.2.0", @@ -68,17 +133,20 @@ "style-loader": "^1.1.3", "typescript": "^4.7.4", "url-loader": "^4.1.1", + "wait-on": "^6.0.1", "webpack": "^5.74.0", "webpack-cli": "^4.10.0", - "webpack-dev-server": "^4.10.0", - "buffer": "^6.0.3" + "electron-builder": "^23.6.0", + "webpack-dev-server": "^4.10.0" }, "dependencies": { "@tarikjabiri/dxf": "^2.3.2", "@types/three": "^0.143.0", + "browser-xml2js": "^0.4.19", "classnames": "2.2.5", "clipper-lib": "6.2.1", "earcut": "2.1.1", + "font-awesome": "4.7.0", "immer": "^9.0.12", "jsketcher-occ-engine": "1.0.1-5efaf53accb45bac475155fc4d5552642c19e91e", @@ -89,6 +157,7 @@ "marked": "^4.0.18", "mousetrap": "1.6.1", "numeric": "1.2.6", + "path": "^0.12.7", "prop-types": "^15.8.1", "react": "^16.13.1", "react-color": "^2.19.3", @@ -97,6 +166,7 @@ "react-toastify": "^5.5.0", "sprintf": "0.1.5", "three": "^0.143.0", - "browser-xml2js": "^0.4.19" - } -} + "url": "^0.11.0" + }, + "proxy":"http://localhost:3000" +} \ No newline at end of file diff --git a/preload.js b/preload.js new file mode 100644 index 00000000..83ececdc --- /dev/null +++ b/preload.js @@ -0,0 +1,10 @@ +// All of the Node.js APIs are available in the preload process. +// It has the same sandbox as a Chrome extension. +const { contextBridge } = require("electron"); + +// As an example, here we use the exposeInMainWorld API to expose the browsers +// and node versions to the main window. +// They'll be accessible at "window.versions". +process.once("loaded", () => { + contextBridge.exposeInMainWorld("versions", process.versions); +}); \ No newline at end of file