diff --git a/BUILDING.md b/BUILDING.md index e3b10861..ac2a1505 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -44,9 +44,9 @@ To build for web deployment: ### Building for Linux -1. First build the web assets: +1. Build the electron app in production mode: ```bash - npm run build + npm run build:electron-prod ``` 2. Package the Electron app for Linux: @@ -58,7 +58,7 @@ To build for web deployment: npm run electron:build-linux-deb ``` -2. The packaged applications will be in `dist-electron-packages/`: +3. The packaged applications will be in `dist-electron-packages/`: - AppImage: `dist-electron-packages/TimeSafari-x.x.x.AppImage` - DEB: `dist-electron-packages/timesafari_x.x.x_amd64.deb` @@ -79,8 +79,13 @@ To build for web deployment: ### Development Testing For testing the Electron build before packaging: + ```bash +# Build and run in development mode (includes DevTools) npm run electron:dev + +# Build in production mode and test +npm run build:electron-prod && npm run electron:start ``` ## Mobile Builds (Capacitor) @@ -145,6 +150,75 @@ To run the application in development mode: ```bash npm run dev ``` +## PyWebView Desktop Build + +### Prerequisites +- Python 3.8 or higher +- pip (Python package manager) +- virtualenv (recommended) +- System dependencies: + ```bash + # For Ubuntu/Debian + sudo apt-get install python3-webview + # or + sudo apt-get install python3-gi python3-gi-cairo gir1.2-gtk-3.0 gir1.2-webkit2-4.0 + + # For Arch Linux + sudo pacman -S webkit2gtk python-gobject python-cairo + + # For Fedora + sudo dnf install python3-webview + # or + sudo dnf install python3-gobject python3-cairo webkit2gtk3 + ``` + +### Setup +1. Create and activate a virtual environment (recommended): + ```bash + python -m venv .venv + source .venv/bin/activate # On Linux/macOS + # or + .venv\Scripts\activate # On Windows + ``` + +2. Install Python dependencies: + ```bash + pip install -r requirements.txt + ``` + +### Troubleshooting + +If encountering PyInstaller version errors: +```bash +# Try installing the latest stable version +pip install --upgrade pyinstaller +``` + +### Development +1. Start the PyWebView development build: + ```bash + npm run pywebview:dev + ``` + +### Building for Distribution + +#### Linux +```bash +npm run pywebview:package-linux +``` +The packaged application will be in `dist/TimeSafari` + +#### Windows +```bash +npm run pywebview:package-win +``` +The packaged application will be in `dist/TimeSafari` + +#### macOS +```bash +npm run pywebview:package-mac +``` +The packaged application will be in `dist/TimeSafari` ## Testing diff --git a/package-lock.json b/package-lock.json index 3ac235ea..5173ae73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -91,6 +91,7 @@ "@vitejs/plugin-vue": "^5.2.1", "@vue/eslint-config-typescript": "^11.0.3", "autoprefixer": "^10.4.19", + "concurrently": "^8.2.2", "electron": "^33.2.1", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", @@ -12159,6 +12160,50 @@ "devOptional": true, "license": "MIT" }, + "node_modules/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/connect": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", @@ -12521,6 +12566,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", @@ -21285,6 +21347,16 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -21836,9 +21908,8 @@ "version": "1.8.2", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "devOptional": true, "license": "MIT", - "optional": true, - "peer": true, "engines": { "node": ">= 0.4" }, @@ -22072,6 +22143,12 @@ "dev": true, "license": "MIT" }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, "node_modules/split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", diff --git a/package.json b/package.json index 447ffe35..20c9d48f 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,12 @@ "electron:build-linux": "electron-builder --linux AppImage", "electron:build-linux-deb": "electron-builder --linux deb", "build:electron-prod": "NODE_ENV=production npm run build:electron", - "electron:build-linux-prod": "npm run build:electron-prod && electron-builder --linux AppImage" + "electron:build-linux-prod": "npm run build:electron-prod && electron-builder --linux AppImage", + "pywebview:dev": "vite build --mode pywebview && .venv/bin/python src/pywebview/main.py", + "pywebview:build": "vite build --mode pywebview && .venv/bin/python src/pywebview/main.py", + "pywebview:package-linux": "vite build --mode pywebview && .venv/bin/python -m PyInstaller --name TimeSafari --add-data 'dist:www' src/pywebview/main.py", + "pywebview:package-win": "vite build --mode pywebview && .venv/Scripts/python -m PyInstaller --name TimeSafari --add-data 'dist;www' src/pywebview/main.py", + "pywebview:package-mac": "vite build --mode pywebview && .venv/bin/python -m PyInstaller --name TimeSafari --add-data 'dist:www' src/pywebview/main.py" }, "dependencies": { "@capacitor/android": "^6.2.0", @@ -109,6 +114,7 @@ "@vitejs/plugin-vue": "^5.2.1", "@vue/eslint-config-typescript": "^11.0.3", "autoprefixer": "^10.4.19", + "concurrently": "^8.2.2", "electron": "^33.2.1", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", @@ -142,7 +148,10 @@ } ], "linux": { - "target": ["AppImage", "deb"], + "target": [ + "AppImage", + "deb" + ], "category": "Office", "icon": "build/icon.png" }, diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..2c0390a8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +pywebview +pyinstaller>=6.12.0 +# For development +watchdog>=3.0.0 # For file watching support \ No newline at end of file diff --git a/src/pywebview/main.py b/src/pywebview/main.py new file mode 100644 index 00000000..007d8c9d --- /dev/null +++ b/src/pywebview/main.py @@ -0,0 +1,59 @@ +import webview +import os +import sys +from http.server import HTTPServer, SimpleHTTPRequestHandler +import threading + +def get_dist_path(): + if getattr(sys, 'frozen', False): + base_path = sys._MEIPASS + else: + base_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + + dist_path = os.path.join(base_path, 'dist') + print(f"Dist path: {dist_path}") + return dist_path + +class CustomHandler(SimpleHTTPRequestHandler): + def __init__(self, *args, **kwargs): + dist_dir = get_dist_path() + print(f"Serving from directory: {dist_dir}") + super().__init__(*args, directory=dist_dir, **kwargs) + + def log_message(self, format, *args): + # Override to show more detailed logging + print(f"Request: {format%args}") + if hasattr(self, 'path'): + print(f"Requested path: {self.path}") + full_path = os.path.join(self.directory, self.path.lstrip('/')) + print(f"Full path: {full_path}") + print(f"File exists: {os.path.exists(full_path)}") + if self.path.endswith('.html'): + print(f"HTML content: {open(full_path).read()[:200]}...") + +def run_server(port=8000): + server_address = ('localhost', port) + httpd = HTTPServer(server_address, CustomHandler) + print(f"Serving files from {get_dist_path()} at http://localhost:{port}") + httpd.serve_forever() + +def main(): + dist_path = get_dist_path() + + # Start local server + server_thread = threading.Thread(target=run_server) + server_thread.daemon = True + server_thread.start() + + # Create window using local server + window = webview.create_window( + 'TimeSafari', + url='http://localhost:8000', + width=1200, + height=800 + ) + + webview.start(debug=True) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/vite.config.mjs b/vite.config.mjs index cc34a1cf..e802c1e1 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -19,14 +19,15 @@ const appConfig = loadAppConfig(); export default defineConfig(({ mode }) => { const isElectron = mode === "electron"; const isCapacitor = mode === "capacitor"; + const isPyWebView = mode === "pywebview"; - // Completely disable PWA features for electron builds - if (isElectron) { + // Disable PWA features for desktop builds + if (isElectron || isPyWebView) { process.env.VITE_PWA_ENABLED = 'false'; } return { - base: isElectron ? "./" : "/", + base: isElectron || isPyWebView ? "./" : "/", server: { port: process.env.VITE_PORT || 8080, fs: {