mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 09:01:03 -08:00
feat(remote-control-web): add new web app for remote control
Add a new remote-control-web application using Angular standalone bootstrap. Add main.ts to bootstrap Application with appConfig and log bootstrap errors. Introduce global styles (styles.scss) to reset page margins, ensure full-height layout, set dark background, and prevent horizontal scroll. Add Nx project configuration (project.json) for build, serve, test, lint, extract-i18n and serve-static targets with production and development configurations, asset and style wiring, and Jest test integration. Add tsconfig.spec.json to configure testing compilation (Jest, Node), include test files and test setup. Start app configuration provider file (app.config.ts) to prepare application providers (partial file added). These changes scaffold the new remote-control-web application and enable building, serving and testing in the monorepo.
This commit is contained in:
1 parent
79b1e991ef
commit
5d2875e554
18 files changed
+483
-2
No files matched your search
@@ -7,7 +7,7 @@
|
||||
"build": {
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": ["web"],
|
||||
"projects": ["web", "remote-control-web"],
|
||||
"target": "build"
|
||||
}
|
||||
],
|
||||
@@ -54,6 +54,7 @@
|
||||
"nx run electron-backend:serve-electron"
|
||||
]
|
||||
}
|
||||
|
||||
},
|
||||
"serve-electron": {
|
||||
"executor": "nx-electron:execute",
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
# Remote Control Web App
|
||||
|
||||
A lightweight, mobile-optimized web application that provides remote control functionality for IPTVnator. Control channel switching from your phone or tablet on the same network.
|
||||
|
||||
## Overview
|
||||
|
||||
This is a standalone Angular application designed to be served by IPTVnator's Electron backend via an HTTP server. It provides a beautiful, Apple TV-inspired remote control interface that communicates with the main IPTVnator desktop application to change channels.
|
||||
|
||||
## Features
|
||||
|
||||
- **Apple TV-Inspired Design** - Premium dark theme with glass morphism effects
|
||||
- **Mobile-Optimized** - Touch-friendly interface designed for phones and tablets
|
||||
- **Real-time Control** - Instant channel switching via REST API
|
||||
- **Minimal & Fast** - Lightweight app (~55 KB compressed) with no external dependencies
|
||||
- **Responsive** - Adapts to different screen sizes (320px - 480px+)
|
||||
|
||||
## How It Works
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐ HTTP ┌──────────────────┐
|
||||
│ Phone/Tablet │ ◄──────────────────► │ HTTP Server │
|
||||
│ (Browser App) │ REST API calls │ (Electron Main) │
|
||||
└─────────────────┘ └──────────────────┘
|
||||
│ IPC
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ Main App │
|
||||
│ (Renderer) │
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
1. **HTTP Server**: Electron backend starts an HTTP server (default port: 8765)
|
||||
2. **Static Files**: Serves this Angular app's built files
|
||||
3. **REST API**: Exposes `/api/remote-control/channel/up` and `/api/remote-control/channel/down` endpoints
|
||||
4. **IPC Communication**: HTTP server sends events to the main Electron window
|
||||
5. **Channel Switching**: Main app receives events and switches to next/previous channel
|
||||
|
||||
### API Endpoints
|
||||
|
||||
- `GET /` - Serves the remote control web interface
|
||||
- `POST /api/remote-control/channel/up` - Switch to previous channel
|
||||
- `POST /api/remote-control/channel/down` - Switch to next channel
|
||||
|
||||
## Usage
|
||||
|
||||
### Enable Remote Control
|
||||
|
||||
1. Open IPTVnator desktop app
|
||||
2. Go to **Settings**
|
||||
3. Enable **"Remote Control"** checkbox
|
||||
4. Optionally change the port (default: 8765)
|
||||
5. The HTTP server starts automatically
|
||||
|
||||
### Access from Mobile Device
|
||||
|
||||
1. Ensure your phone/tablet is on the **same network** as your computer
|
||||
2. Find your computer's IP address:
|
||||
- **Windows**: `ipconfig` (look for IPv4)
|
||||
- **macOS**: `ifconfig` or System Preferences → Network
|
||||
- **Linux**: `ip addr` or `hostname -I`
|
||||
3. Open browser on your mobile device
|
||||
4. Navigate to: `http://<YOUR_IP>:8765`
|
||||
- Example: `http://192.168.1.100:8765`
|
||||
|
||||
### Controls
|
||||
|
||||
- **Top half of circle**: Previous channel (Channel Up)
|
||||
- **Bottom half of circle**: Next channel (Channel Down)
|
||||
- **Center blue dot**: Status indicator (glowing = ready)
|
||||
|
||||
## Development
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
# Production build
|
||||
nx build remote-control-web --configuration=production
|
||||
|
||||
# Development build
|
||||
nx build remote-control-web
|
||||
```
|
||||
|
||||
Built files are output to `dist/apps/remote-control-web/browser/`
|
||||
|
||||
### Serve Locally
|
||||
|
||||
The app is designed to be served by the Electron backend's HTTP server. To test:
|
||||
|
||||
```bash
|
||||
# Start Electron backend (includes HTTP server)
|
||||
nx serve electron-backend
|
||||
|
||||
# Access at http://localhost:8765
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
apps/remote-control-web/
|
||||
├── src/
|
||||
│ ├── app/
|
||||
│ │ ├── app.component.ts # Root component
|
||||
│ │ ├── app.config.ts # Angular configuration
|
||||
│ │ └── app.routes.ts # Routes (single route)
|
||||
│ ├── assets/ # Static assets (if any)
|
||||
│ ├── index.html # HTML entry point
|
||||
│ └── main.ts # Bootstrap Angular app
|
||||
├── project.json # Nx project configuration
|
||||
├── tsconfig.json # TypeScript config
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Shared UI Library
|
||||
|
||||
The remote control component is located in:
|
||||
```
|
||||
libs/ui/remote-control/
|
||||
├── src/lib/remote-control/
|
||||
│ ├── remote-control.component.ts # Main component logic
|
||||
│ ├── remote-control.component.html # Template
|
||||
│ ├── remote-control.component.scss # Apple TV-inspired styles
|
||||
│ └── remote-control.service.ts # HTTP service for API calls
|
||||
```
|
||||
|
||||
This library is shared and can be reused in other applications if needed.
|
||||
|
||||
## Technical Details
|
||||
|
||||
- **Framework**: Angular 18+ (Standalone components)
|
||||
- **HTTP Client**: Angular HttpClient
|
||||
- **Styling**: SCSS with BEM methodology
|
||||
- **Design System**: Apple TV-inspired with glass morphism
|
||||
- **Animations**: CSS-only (no JavaScript animations)
|
||||
- **Bundle Size**: ~55 KB gzipped (production build)
|
||||
- **Browser Support**: Modern browsers (Chrome, Safari, Firefox, Edge)
|
||||
|
||||
## Design Philosophy
|
||||
|
||||
The interface is inspired by Apple TV's remote control:
|
||||
- **Minimalism**: Clean, uncluttered design with essential controls only
|
||||
- **Glass Morphism**: Frosted glass effects with subtle reflections
|
||||
- **Touch-Optimized**: Large touch targets (50% of circle per button)
|
||||
- **Feedback**: Visual feedback on hover/press with ripple effects
|
||||
- **Accessibility**: Supports reduced motion preferences
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- **Local Network Only**: The HTTP server should only be accessible on your local network
|
||||
- **No Authentication**: Currently no authentication - ensure your network is trusted
|
||||
- **CORS**: Server accepts requests from any origin on the local network
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential improvements:
|
||||
- Volume control
|
||||
- Play/pause functionality
|
||||
- Channel search
|
||||
- Favorites quick access
|
||||
- Authentication/PIN protection
|
||||
- WebSocket for real-time status updates
|
||||
- Multi-device support with channel sync
|
||||
|
||||
## Related Files
|
||||
|
||||
- **HTTP Server**: `apps/electron-backend/src/app/server/http-server.ts`
|
||||
- **Remote Control Events**: `apps/electron-backend/src/app/events/remote-control.events.ts`
|
||||
- **Main App Handler**: `apps/web/src/app/home/video-player/video-player.component.ts` (handleRemoteChannelChange method)
|
||||
- **Settings UI**: `apps/web/src/app/settings/settings.component.html`
|
||||
|
||||
## License
|
||||
|
||||
Same as IPTVnator main project.
|
||||
@@ -0,0 +1,34 @@
|
||||
import nx from '@nx/eslint-plugin';
|
||||
import baseConfig from '../../eslint.config.mjs';
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
...nx.configs['flat/angular'],
|
||||
...nx.configs['flat/angular-template'],
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'attribute',
|
||||
prefix: 'app',
|
||||
style: 'camelCase',
|
||||
},
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'element',
|
||||
prefix: 'app',
|
||||
style: 'kebab-case',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.html'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
displayName: 'remote-control-web',
|
||||
preset: '../../jest.preset.js',
|
||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||
coverageDirectory: '../../coverage/apps/remote-control-web',
|
||||
transform: {
|
||||
'^.+\\.(ts|mjs|js|html)$': [
|
||||
'jest-preset-angular',
|
||||
{
|
||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
},
|
||||
],
|
||||
},
|
||||
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
||||
snapshotSerializers: [
|
||||
'jest-preset-angular/build/serializers/no-ng-attributes',
|
||||
'jest-preset-angular/build/serializers/ng-snapshot',
|
||||
'jest-preset-angular/build/serializers/html-comment',
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"name": "remote-control-web",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"projectType": "application",
|
||||
"prefix": "app",
|
||||
"sourceRoot": "apps/remote-control-web/src",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@angular/build:application",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/apps/remote-control-web",
|
||||
"browser": "apps/remote-control-web/src/main.ts",
|
||||
"polyfills": ["zone.js"],
|
||||
"tsConfig": "apps/remote-control-web/tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/remote-control-web/public"
|
||||
},
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "apps/web/src/assets/i18n",
|
||||
"output": "assets/i18n"
|
||||
}
|
||||
],
|
||||
"styles": ["apps/remote-control-web/src/styles.scss"]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kb",
|
||||
"maximumError": "1mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "4kb",
|
||||
"maximumError": "8kb"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"continuous": true,
|
||||
"executor": "@angular/build:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "remote-control-web:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "remote-control-web:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"executor": "@angular/build:extract-i18n",
|
||||
"options": {
|
||||
"buildTarget": "remote-control-web:build"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint"
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "apps/remote-control-web/jest.config.ts",
|
||||
"tsConfig": "apps/remote-control-web/tsconfig.spec.json"
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"continuous": true,
|
||||
"executor": "@nx/web:file-server",
|
||||
"options": {
|
||||
"buildTarget": "remote-control-web:build",
|
||||
"port": 4200,
|
||||
"staticFilePath": "dist/apps/remote-control-web/browser",
|
||||
"spa": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
ApplicationConfig,
|
||||
importProvidersFrom,
|
||||
provideBrowserGlobalErrorListeners,
|
||||
provideZoneChangeDetection,
|
||||
} from '@angular/core';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
|
||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
export function HttpLoaderFactory(http: HttpClient) {
|
||||
return new TranslateHttpLoader(http, './assets/i18n/', '.json');
|
||||
}
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideBrowserGlobalErrorListeners(),
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideHttpClient(),
|
||||
importProvidersFrom(
|
||||
TranslateModule.forRoot({
|
||||
defaultLanguage: 'en',
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: HttpLoaderFactory,
|
||||
deps: [HttpClient],
|
||||
},
|
||||
})
|
||||
),
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
<lib-remote-control />
|
||||
@@ -0,0 +1,4 @@
|
||||
:host {
|
||||
display: block;
|
||||
height: 100vh;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { RemoteControlComponent } from 'remote-control';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
imports: [RemoteControlComponent],
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.html',
|
||||
styleUrl: './app.scss',
|
||||
})
|
||||
export class App implements OnInit {
|
||||
private translate = inject(TranslateService);
|
||||
|
||||
ngOnInit() {
|
||||
// Set default language
|
||||
this.translate.setDefaultLang('en');
|
||||
this.translate.use('en');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>remote-control-web</title>
|
||||
<base href="/" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,5 @@
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import { appConfig } from './app/app.config';
|
||||
import { App } from './app/app';
|
||||
|
||||
bootstrapApplication(App, appConfig).catch((err) => console.error(err));
|
||||
@@ -0,0 +1,11 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
|
||||
html,
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #1a1a1a;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
|
||||
setupZoneTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": []
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": [
|
||||
"jest.config.ts",
|
||||
"src/test-setup.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"target": "es2022",
|
||||
"moduleResolution": "bundler",
|
||||
"isolatedModules": true,
|
||||
"emitDecoratorMetadata": false,
|
||||
"module": "preserve"
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"typeCheckHostBindings": true,
|
||||
"strictTemplates": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"target": "es2016",
|
||||
"types": ["jest", "node"],
|
||||
"moduleResolution": "node10"
|
||||
},
|
||||
"files": ["src/test-setup.ts"],
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
+2
-1
@@ -22,7 +22,8 @@
|
||||
"m3u-utils": ["libs/shared/m3u-utils/src/index.ts"],
|
||||
"services": ["libs/services/src/index.ts"],
|
||||
"shared-interfaces": ["libs/shared/interfaces/src/index.ts"],
|
||||
"shared-portals": ["libs/ui/shared-portals/src/index.ts"]
|
||||
"shared-portals": ["libs/ui/shared-portals/src/index.ts"],
|
||||
"remote-control": ["libs/ui/remote-control/src/index.ts"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "tmp"],
|
||||
|
||||
Reference in new issue
Block a user