Compare commits
16 Commits
8bf3343ba3
...
f0e6b704cd
| Author | SHA1 | Date |
|---|---|---|
|
|
f0e6b704cd | |
|
|
c0f0986a5f | |
|
|
c13a4ff356 | |
|
|
51b47beba9 | |
|
|
be819cd1bb | |
|
|
f7e77f895c | |
|
|
fc9820217b | |
|
|
c2213e0969 | |
|
|
f499cbfa7b | |
|
|
f3eb099e18 | |
|
|
d0e8ded221 | |
|
|
11398c6869 | |
|
|
aa00c75edf | |
|
|
d42a26ad8e | |
|
|
bb8b654e93 | |
|
|
70cd30af95 |
Binary file not shown.
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
|
|
@ -7,7 +7,8 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm-run-all --parallel dev:react dev:electron",
|
"dev": "npm-run-all --parallel dev:react dev:electron",
|
||||||
"dev:react": "vite",
|
"dev:react": "vite",
|
||||||
"dev:electron": "npm run transpile:electron && cross-env NODE_ENV=development electron --inspect=5858 .",
|
"dev:electron": "npm run build && cross-env NODE_ENV=development electron .",
|
||||||
|
"prod:electron": "npm run build && cross-env NODE_ENV=production electron .",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"transpile:electron": "tsc --project src/electron/tsconfig.json",
|
"transpile:electron": "tsc --project src/electron/tsconfig.json",
|
||||||
|
|
@ -20,6 +21,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@reduxjs/toolkit": "^2.10.1",
|
"@reduxjs/toolkit": "^2.10.1",
|
||||||
"@tailwindcss/vite": "^4.1.16",
|
"@tailwindcss/vite": "^4.1.16",
|
||||||
|
"active-win": "^7.2.0",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
"electron-store": "^11.0.2",
|
"electron-store": "^11.0.2",
|
||||||
|
|
@ -33,12 +35,14 @@
|
||||||
"react-router-dom": "^7.9.5",
|
"react-router-dom": "^7.9.5",
|
||||||
"recharts": "^2.12.7",
|
"recharts": "^2.12.7",
|
||||||
"sharp": "^0.34.4",
|
"sharp": "^0.34.4",
|
||||||
"tailwindcss": "^4.1.16"
|
"socket.io-client": "^4.8.1",
|
||||||
|
"tailwindcss": "^4.1.16",
|
||||||
|
"windowmanager": "^0.14.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.47.2",
|
"@playwright/test": "^1.47.2",
|
||||||
"@types/luxon": "^3.7.1",
|
"@types/luxon": "^3.7.1",
|
||||||
"@types/node": "^22.5.5",
|
"@types/node": "^22.19.0",
|
||||||
"@types/os-utils": "^0.0.4",
|
"@types/os-utils": "^0.0.4",
|
||||||
"@types/react": "^18.3.8",
|
"@types/react": "^18.3.8",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
|
|
@ -47,6 +51,7 @@
|
||||||
"daisyui": "^5.4.3",
|
"daisyui": "^5.4.3",
|
||||||
"electron": "^32.1.2",
|
"electron": "^32.1.2",
|
||||||
"electron-builder": "^25.0.5",
|
"electron-builder": "^25.0.5",
|
||||||
|
"electron-rebuild": "^3.2.9",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.2",
|
||||||
"vite": "^5.4.7",
|
"vite": "^5.4.7",
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 27 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 27 KiB |
|
|
@ -0,0 +1,29 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: #ffffff;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
.loader {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border: 5px solid #e0e0e0;
|
||||||
|
border-top-color: #6a5acd;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="loader"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { ipcMain, BrowserWindow } from "electron";
|
import { ipcMain, BrowserWindow } from "electron";
|
||||||
import { getToken, saveToken, clearToken } from "../utils/tokenHelper.js";
|
import { getToken, saveToken, clearToken } from "../utils/tokenHelper.js";
|
||||||
import { getActiveApp, handleLogin, takeScreenshot, startWindowPolling } from "../utils/utilityFunctions.js";
|
import { getActiveApp, handleLogin, takeScreenshot, startWindowPolling, getRunningAppsList, captureSpecificApp } from "../utils/utilityFunctions.js";
|
||||||
import api from "../api/axiosClient.js";
|
import api from "../api/axiosClient.js";
|
||||||
import { getMainWindow } from '../main.js';
|
import { getMainWindow } from '../main.js';
|
||||||
|
|
||||||
|
|
@ -29,7 +29,7 @@ export const registerIPCHandlers = (mainWindow: BrowserWindow) => {
|
||||||
return { success: true };
|
return { success: true };
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle("login-request", async (_, credentials: { email: string; password: string }) => {
|
ipcMain.handle("login-request", async (_, credentials: { email: string; password: string }) => {
|
||||||
return handleLogin(credentials);
|
return handleLogin(credentials);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -83,7 +83,7 @@ export const registerIPCHandlers = (mainWindow: BrowserWindow) => {
|
||||||
|
|
||||||
|
|
||||||
ipcMain.handle("get-project-list", async () => {
|
ipcMain.handle("get-project-list", async () => {
|
||||||
// console.log("📡 get-project-list called");
|
console.log("📡 get-project-list called");
|
||||||
try {
|
try {
|
||||||
const res = await api.get("/list/projects");
|
const res = await api.get("/list/projects");
|
||||||
|
|
||||||
|
|
@ -226,6 +226,14 @@ ipcMain.handle("forgot-password", async (event, email: string) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
ipcMain.handle('get-running-apps', async () => {
|
||||||
|
return await getRunningAppsList()
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('capture-specific-window', async (event, appTitle: string) => {
|
||||||
|
console.log(appTitle, "IPC HANDLE")
|
||||||
|
return await captureSpecificApp(appTitle)
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
ipcMain.handle("get-profile", async () => {
|
ipcMain.handle("get-profile", async () => {
|
||||||
|
|
|
||||||
|
|
@ -1,98 +1,179 @@
|
||||||
import { app, BrowserWindow, ipcMain, session } from 'electron';
|
import { app, BrowserWindow, globalShortcut, session } from "electron";
|
||||||
import { ipcMainHandle, ipcMainOn, isDev } from './util.js';
|
import { isDev } from "./util.js";
|
||||||
import { getStaticData, pollResources } from './resourceManager.js';
|
// import { pollResources } from "./resourceManager.js";
|
||||||
import { getPreloadPath, getUIPath } from './pathResolver.js';
|
import { getPreloadPath, getUIPath } from "./pathResolver.js";
|
||||||
|
import { registerIPCHandlers } from "./ipc/ipcHandler.js";
|
||||||
|
|
||||||
import "./ipc/ipcHandler.js";
|
import "./ipc/ipcHandler.js";
|
||||||
import { registerIPCHandlers } from './ipc/ipcHandler.js';
|
import path from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
let mainWindow:any = null;
|
app.setAppUserModelId("com.tracker.app");
|
||||||
|
|
||||||
app.on('ready', async () => {
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
console.log("ready");
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
mainWindow = new BrowserWindow({
|
// ---------------------------
|
||||||
width: 500, // fixed width
|
// SINGLE INSTANCE LOCK
|
||||||
height: 600, // fixed height
|
// ---------------------------
|
||||||
|
const gotTheLock = app.requestSingleInstanceLock();
|
||||||
resizable: false, // prevents resizing
|
|
||||||
fullscreenable: false, // disables fullscreen toggle (F11 or double-click title bar)
|
if (!gotTheLock) {
|
||||||
minimizable: false, // disables minimize button
|
app.quit();
|
||||||
maximizable: false, // prevents maximize
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mainWindow: BrowserWindow | null = null;
|
||||||
|
let splashWindow: BrowserWindow | null = null;
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// HANDLE DOUBLE OPEN ATTEMPT
|
||||||
|
// ---------------------------
|
||||||
|
app.on("second-instance", () => {
|
||||||
|
if (mainWindow) {
|
||||||
|
if (mainWindow.isMinimized()) mainWindow.restore();
|
||||||
|
mainWindow.show();
|
||||||
|
mainWindow.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// MAIN APP READY
|
||||||
|
// ---------------------------
|
||||||
|
app.on("ready", async () => {
|
||||||
|
|
||||||
|
|
||||||
|
splashWindow = new BrowserWindow({
|
||||||
|
width: 300,
|
||||||
|
height: 300,
|
||||||
|
frame: false,
|
||||||
|
transparent: false,
|
||||||
|
alwaysOnTop: false, // splash does NOT need to be on top || Yooo Don't make it onTop
|
||||||
|
resizable: false,
|
||||||
|
show: true,
|
||||||
|
skipTaskbar: true,
|
||||||
|
webPreferences: {
|
||||||
|
devTools: false,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
splashWindow.loadFile(
|
||||||
|
path.join(__dirname, "../assets/splash.html")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create the main window
|
||||||
|
const win = new BrowserWindow({
|
||||||
|
width: 500,
|
||||||
|
height: 600,
|
||||||
|
resizable: false,
|
||||||
|
fullscreenable: false,
|
||||||
|
minimizable: false,
|
||||||
|
maximizable: false,
|
||||||
closable: true,
|
closable: true,
|
||||||
alwaysOnTop: true,
|
alwaysOnTop: true,
|
||||||
|
skipTaskbar: true,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
|
frame: process.platform === "darwin" ? false : true,
|
||||||
frame: process.platform === 'darwin' ? false : true,
|
show: false, // speed improvement
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
|
devTools: false,
|
||||||
preload: getPreloadPath(),
|
preload: getPreloadPath(),
|
||||||
|
contextIsolation: true,
|
||||||
|
nodeIntegration: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
session.defaultSession.webRequest.onBeforeRequest((details, callback) => {
|
mainWindow = win;
|
||||||
console.log('🌐 Request:', details.method, details.url);
|
|
||||||
callback({ cancel: false });
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isDev()) {
|
// ------------------------------------
|
||||||
mainWindow.loadURL('http://localhost:5123');
|
// ONLY LOG NETWORK REQUESTS IN DEV
|
||||||
mainWindow.webContents.openDevTools();
|
// ------------------------------------
|
||||||
} else {
|
if (isDev() && process.env.LOG_REQUESTS === "true") {
|
||||||
mainWindow.loadFile(getUIPath());
|
session.defaultSession.webRequest.onBeforeRequest((details, callback) => {
|
||||||
|
console.log("🌐 Request:", details.method, details.url);
|
||||||
|
callback({ cancel: false });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// pollResources(mainWindow);
|
// ------------------------------------
|
||||||
// 🔹 Register all IPC handlers (includes polling)
|
// LOAD FRONTEND
|
||||||
registerIPCHandlers(mainWindow);
|
// ------------------------------------
|
||||||
|
if (isDev()) {
|
||||||
|
win.loadURL("http://localhost:5123");
|
||||||
|
// win.webContents.openDevTools(); // Optional (slows startup)
|
||||||
|
} else {
|
||||||
|
win.loadFile(getUIPath());
|
||||||
|
}
|
||||||
|
|
||||||
pollResources(mainWindow);
|
// ------------------------------------
|
||||||
|
// SHOW WINDOW WHEN READY (no white flash)
|
||||||
|
// ------------------------------------
|
||||||
|
win.once("ready-to-show", () => {
|
||||||
|
// Delay heavy work to improve startup speed
|
||||||
|
setTimeout(() => {
|
||||||
|
splashWindow?.close();
|
||||||
|
splashWindow = null;
|
||||||
|
|
||||||
// IPC handlers
|
win.show();
|
||||||
// ipcMainHandle('getStaticData', () => getStaticData());
|
// pollResources(win);
|
||||||
|
registerIPCHandlers(win);
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
|
||||||
// ipcMainOn('sendFrameAction', (payload) => {
|
// ------------------------------------
|
||||||
// switch (payload) {
|
// HANDLE CLOSE EVENTS
|
||||||
// case 'CLOSE':
|
// ------------------------------------
|
||||||
// mainWindow.close();
|
handleCloseEvents(win);
|
||||||
// break;
|
|
||||||
// case 'MAXIMIZE':
|
|
||||||
// // prevent maximize
|
|
||||||
// break;
|
|
||||||
// case 'MINIMIZE':
|
|
||||||
// mainWindow.minimize();
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
handleCloseEvents(mainWindow);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleCloseEvents(mainWindow: BrowserWindow) {
|
|
||||||
|
app.on('browser-window-focus', () => {
|
||||||
|
globalShortcut.register('CommandOrControl+R', () => {
|
||||||
|
// Do nothing; disables reload
|
||||||
|
});
|
||||||
|
globalShortcut.register('F5', () => {
|
||||||
|
// Do nothing; disables reload
|
||||||
|
});
|
||||||
|
globalShortcut.register('CommandOrControl+Shift+I', () => {
|
||||||
|
// Do nothing; disables DevTools
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('browser-window-blur', () => {
|
||||||
|
globalShortcut.unregisterAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// CUSTOM CLOSE BEHAVIOR
|
||||||
|
// ---------------------------
|
||||||
|
function handleCloseEvents(window: BrowserWindow) {
|
||||||
let willClose = false;
|
let willClose = false;
|
||||||
|
|
||||||
mainWindow.on('close', (e) => {
|
window.on("close", (e) => {
|
||||||
if (willClose) return;
|
if (willClose) return;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
mainWindow.hide();
|
window.hide();
|
||||||
|
|
||||||
if (app.dock) {
|
if (app.dock) {
|
||||||
app.dock.hide();
|
app.dock.hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('before-quit', () => {
|
app.on("before-quit", () => {
|
||||||
willClose = true;
|
willClose = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
mainWindow.on('show', () => {
|
window.on("show", () => {
|
||||||
willClose = false;
|
willClose = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// GET MAIN WINDOW EXPORT
|
||||||
|
// ---------------------------
|
||||||
export function getMainWindow() {
|
export function getMainWindow() {
|
||||||
return mainWindow;
|
return mainWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,10 @@ export function getPreloadPath() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUIPath() {
|
export function getUIPath() {
|
||||||
return path.join(app.getAppPath(), '/dist-react/index.html');
|
return path.join(app.getAppPath(), "dist-react", "index.html");
|
||||||
|
|
||||||
|
// return "C:\\Users\\user\\OneDrive\\Desktop\\electron-main\\dist-react\\index.html";
|
||||||
|
// return path.join(app.getAppPath(), '/dist-react/index.html');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAssetPath() {
|
export function getAssetPath() {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ contextBridge.exposeInMainWorld('electron', {
|
||||||
toggleCloseBtn: (shouldEnable: boolean) => ipcRenderer.invoke('toggle-close-btn', shouldEnable),
|
toggleCloseBtn: (shouldEnable: boolean) => ipcRenderer.invoke('toggle-close-btn', shouldEnable),
|
||||||
resizeWindow: (width: number, height: number) => ipcRenderer.invoke('resize-window', width, height),
|
resizeWindow: (width: number, height: number) => ipcRenderer.invoke('resize-window', width, height),
|
||||||
getProjectList: () => ipcRenderer.invoke("get-project-list"),
|
getProjectList: () => ipcRenderer.invoke("get-project-list"),
|
||||||
|
getRunningApps: () => ipcRenderer.invoke('get-running-apps'),
|
||||||
|
captureAppScreenShot: (appTitle: string) : Promise<string> => ipcRenderer.invoke('capture-specific-window', appTitle),
|
||||||
onWindowUpdate: (callback) =>
|
onWindowUpdate: (callback) =>
|
||||||
ipcRenderer.on("window:updated", (_, data) => callback(data)),
|
ipcRenderer.on("window:updated", (_, data) => callback(data)),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import { BrowserWindow, desktopCapturer } from "electron";
|
import { app, BrowserWindow, desktopCapturer } from "electron";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
import { saveToken } from "./tokenHelper.js";
|
import { saveToken } from "./tokenHelper.js";
|
||||||
import { activeWindow } from "get-windows";
|
import { activeWindow } from "get-windows";
|
||||||
|
import { exec } from "child_process";
|
||||||
|
import { getUIPath } from "../pathResolver.js";
|
||||||
|
|
||||||
// Blur using an offscreen canvas
|
// Blur using an offscreen canvas
|
||||||
async function blurAndCompressBase64Image(base64: string): Promise<Buffer> {
|
async function blurAndCompressBase64Image(base64: string): Promise<Buffer> {
|
||||||
|
|
@ -53,6 +55,12 @@ export const handleLogin = async (credentials: { email: string, password: string
|
||||||
// Call your login API
|
// Call your login API
|
||||||
console.log("🟡 login Request received", credentials);
|
console.log("🟡 login Request received", credentials);
|
||||||
|
|
||||||
|
console.log("Doom");
|
||||||
|
console.log("app.getAppPath()", app.getAppPath());
|
||||||
|
console.log("UIPATH", getUIPath());
|
||||||
|
console.log("Doom 1");
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log("➡️ Calling API...");
|
console.log("➡️ Calling API...");
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
|
|
@ -97,23 +105,81 @@ export const handleLogin = async (credentials: { email: string, password: string
|
||||||
|
|
||||||
// Get active window info with timestamp
|
// Get active window info with timestamp
|
||||||
export const getActiveApp = async () => {
|
export const getActiveApp = async () => {
|
||||||
const result = await activeWindow();
|
const result = await activeWindow();
|
||||||
const dataWithTime = { ...result, timestamp: new Date().toISOString() };
|
const dataWithTime = { ...result, timestamp: new Date().toISOString() };
|
||||||
// console.log("🪟 Active Window with Time:", dataWithTime);
|
// console.log("🪟 Active Window with Time:", dataWithTime);
|
||||||
return dataWithTime;
|
return dataWithTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Periodically send active window updates
|
// Periodically send active window updates
|
||||||
export const startWindowPolling = (win: BrowserWindow) => {
|
export const startWindowPolling = (win: BrowserWindow) => {
|
||||||
console.log("⏱️ Started active window polling (every 5s)");
|
console.log("⏱️ Started active window polling (every 5s)");
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
const app = await getActiveApp();
|
const app = await getActiveApp();
|
||||||
if (app) win.webContents.send("window:updated", app);
|
if (app) win.webContents.send("window:updated", app);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("❌ Polling error:", err);
|
console.error("❌ Polling error:", err);
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const getRunningAppsList = async () => {
|
||||||
|
try {
|
||||||
|
// PowerShell command: Get only apps with visible windows (non-background)
|
||||||
|
const sources = await desktopCapturer.getSources({
|
||||||
|
types: ['window', 'screen'],
|
||||||
|
thumbnailSize: { width: 150, height: 150 } // Optional: set thumbnail size
|
||||||
|
});
|
||||||
|
|
||||||
|
const app = sources.map(source => ({
|
||||||
|
name: source.name,
|
||||||
|
pid: source.id,
|
||||||
|
// thumbnail: source.thumbnail // Can be used as screenshot
|
||||||
|
}));
|
||||||
|
return app
|
||||||
|
} catch (error) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const captureSpecificApp = async (windowTitle: string): Promise<string> => {
|
||||||
|
try {
|
||||||
|
const sources = await desktopCapturer.getSources({
|
||||||
|
types: ['window', 'screen'],
|
||||||
|
thumbnailSize: { width: 650, height: 430 }
|
||||||
|
});
|
||||||
|
const targetSource = sources.find(source => source.name === windowTitle);
|
||||||
|
if (targetSource) {
|
||||||
|
|
||||||
|
let image = targetSource.thumbnail.toDataURL();
|
||||||
|
const blurredBuffer = await blurAndCompressBase64Image(image);
|
||||||
|
|
||||||
|
// Convert buffer → base64 string
|
||||||
|
const base64String = `data:image/png;base64,${blurredBuffer.toString('base64')}`;
|
||||||
|
|
||||||
|
// ✅ Return the processed base64 instead of saving
|
||||||
|
return base64String;
|
||||||
|
|
||||||
|
// const folderPath = "C:\\Users\\Public\\vatracker\\screenshots";
|
||||||
|
|
||||||
|
// // Ensure directory exists
|
||||||
|
// if (!fs.existsSync(folderPath)) {
|
||||||
|
// fs.mkdirSync(folderPath, { recursive: true });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const filePath = path.join(folderPath, `screenshot-${Date.now()}.png`);
|
||||||
|
// fs.writeFileSync(filePath, blurredBuffer);
|
||||||
|
|
||||||
|
// console.log(`Screenshot saved to ${filePath}`);
|
||||||
|
} else {
|
||||||
|
console.log(`Window with title "${windowTitle}" not found.`);
|
||||||
|
return "Not Found"
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error capturing screenshot:', error);
|
||||||
|
return 'Failed to capture screenshot.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,84 @@
|
||||||
|
// import React, { useEffect } from "react";
|
||||||
|
// import { BrowserRouter, Route, Routes, Navigate } from "react-router-dom";
|
||||||
|
// import { useDispatch, useSelector } from "react-redux";
|
||||||
|
// import { AppDispatch, RootState } from "./store";
|
||||||
|
// import { loadUserFromStorage } from "./store/userSlice";
|
||||||
|
|
||||||
|
// import Login from "./pages/Login";
|
||||||
|
// import Dashboard from "./pages/Dashboard";
|
||||||
|
// import Projects from "./pages/Projects/Projects";
|
||||||
|
// import DashboardLayout from "./layout/DashboardLayout";
|
||||||
|
// import ProtectedRoute from "./routes/ProtectedRoute";
|
||||||
|
// import PublicRoute from "./routes/PublicRoute";
|
||||||
|
// import { ThemeToggle } from "./helper/ThemeToggle";
|
||||||
|
// import LeaveAndAdjustments from "./pages/LeaveAndAdjustments/LeaveAndAdjustments";
|
||||||
|
// import Timesheets from "./pages/Timesheets/Timesheets";
|
||||||
|
// import Settings from "./pages/Settings/Settings";
|
||||||
|
// import HelpSupport from "./pages/HelpSupport/HelpSupport";
|
||||||
|
|
||||||
|
// const App: React.FC = () => {
|
||||||
|
// const theme = ThemeToggle();
|
||||||
|
// const dispatch = useDispatch<AppDispatch>();
|
||||||
|
|
||||||
|
// // ✅ Get user directly from Redux
|
||||||
|
// const user = useSelector((state: RootState) => state.user.user);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// document.documentElement.setAttribute("data-theme", theme);
|
||||||
|
// }, [theme]);
|
||||||
|
|
||||||
|
// // ✅ Load user from localStorage into Redux when app starts
|
||||||
|
// useEffect(() => {
|
||||||
|
// dispatch(loadUserFromStorage());
|
||||||
|
// }, [dispatch]);
|
||||||
|
|
||||||
|
// console.log("App.tsx → user:", user);
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <BrowserRouter>
|
||||||
|
// <Routes>
|
||||||
|
// {/* Public Route */}
|
||||||
|
// <Route
|
||||||
|
// path="/login"
|
||||||
|
// element={
|
||||||
|
// <PublicRoute user={user}>
|
||||||
|
// <Login />
|
||||||
|
// </PublicRoute>
|
||||||
|
// }
|
||||||
|
// />
|
||||||
|
|
||||||
|
// {/* Protected Routes */}
|
||||||
|
// <Route
|
||||||
|
// path="/dashboard"
|
||||||
|
// element={
|
||||||
|
// <ProtectedRoute user={user}>
|
||||||
|
// <DashboardLayout />
|
||||||
|
// </ProtectedRoute>
|
||||||
|
// }
|
||||||
|
// >
|
||||||
|
// <Route index element={<Dashboard />} />
|
||||||
|
// <Route path="projects" element={<Projects />} />
|
||||||
|
// <Route path="leave-adjustments" element={<LeaveAndAdjustments />} />
|
||||||
|
// <Route path="timesheet" element={<Timesheets />} />
|
||||||
|
// <Route path="settings" element={<Settings />} />
|
||||||
|
// <Route path="help-support" element={<HelpSupport />} />
|
||||||
|
// </Route>
|
||||||
|
|
||||||
|
// {/* Default Redirect */}
|
||||||
|
// <Route
|
||||||
|
// path="*"
|
||||||
|
// element={<Navigate to={user ? "/dashboard" : "/login"} replace />}
|
||||||
|
// />
|
||||||
|
// </Routes>
|
||||||
|
// </BrowserRouter>
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export default App;
|
||||||
|
|
||||||
|
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { BrowserRouter, Route, Routes, Navigate } from "react-router-dom";
|
import { HashRouter as Router, Route, Routes, Navigate } from "react-router-dom";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { AppDispatch, RootState } from "./store";
|
import { AppDispatch, RootState } from "./store";
|
||||||
import { loadUserFromStorage } from "./store/userSlice";
|
import { loadUserFromStorage } from "./store/userSlice";
|
||||||
|
|
@ -20,8 +99,6 @@ const App: React.FC = () => {
|
||||||
const theme = ThemeToggle();
|
const theme = ThemeToggle();
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const dispatch = useDispatch<AppDispatch>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ✅ Get user directly from Redux
|
// ✅ Get user directly from Redux
|
||||||
const user = useSelector((state: RootState) => state.user.user);
|
const user = useSelector((state: RootState) => state.user.user);
|
||||||
|
|
||||||
|
|
@ -29,15 +106,14 @@ const App: React.FC = () => {
|
||||||
document.documentElement.setAttribute("data-theme", theme);
|
document.documentElement.setAttribute("data-theme", theme);
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
// ✅ Load user from localStorage into Redux when app starts
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(loadUserFromStorage());
|
dispatch(loadUserFromStorage());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
console.log("App.tsx → user:", user);
|
console.log("App.tsx → user:", user);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<Router>
|
||||||
<Routes>
|
<Routes>
|
||||||
{/* Public Route */}
|
{/* Public Route */}
|
||||||
<Route
|
<Route
|
||||||
|
|
@ -72,7 +148,7 @@ const App: React.FC = () => {
|
||||||
element={<Navigate to={user ? "/dashboard" : "/login"} replace />}
|
element={<Navigate to={user ? "/dashboard" : "/login"} replace />}
|
||||||
/>
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</Router>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
|
import io, { Socket } from 'socket.io-client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Outlet,
|
Outlet,
|
||||||
NavLink,
|
NavLink,
|
||||||
|
|
@ -124,6 +126,11 @@ const DashboardLayout = () => {
|
||||||
selectedProject?.projectName ?? null
|
selectedProject?.projectName ?? null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const socketRef = useRef<Socket | null>(null);
|
||||||
|
const intervalRef = useRef<number | null>(null);
|
||||||
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
|
// const [seclectedScreen, setSelectedScreen] = useState<string | null>(null)
|
||||||
|
|
||||||
const [projectsData, setProjectsData] = useState<Project[]>([]);
|
const [projectsData, setProjectsData] = useState<Project[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -255,7 +262,7 @@ const DashboardLayout = () => {
|
||||||
dispatch(clearUser()); // ✅ Removes from Redux + localStorage
|
dispatch(clearUser()); // ✅ Removes from Redux + localStorage
|
||||||
navigate("/login");
|
navigate("/login");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Logout error:", error);
|
console.error("Logout error:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -559,7 +566,22 @@ const DashboardLayout = () => {
|
||||||
{isTracking &&
|
{isTracking &&
|
||||||
<header className="w-full flex justify-between items-center h-12 px-6 bg-base-100 border-b border-base-300 sticky top-0 z-30">
|
<header className="w-full flex justify-between items-center h-12 px-6 bg-base-100 border-b border-base-300 sticky top-0 z-30">
|
||||||
{/* Left Section */}
|
{/* Left Section */}
|
||||||
<div className="flex items-center space-x-3">
|
{/* {!isConnected ? (
|
||||||
|
<button
|
||||||
|
onClick={handleConnect}
|
||||||
|
className="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded"
|
||||||
|
>
|
||||||
|
Connect
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={handleDisconnect}
|
||||||
|
className="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded"
|
||||||
|
>
|
||||||
|
Disconnect
|
||||||
|
</button>
|
||||||
|
)} */}
|
||||||
|
<div className="flex items-center space-x-3">
|
||||||
{selectedProject ? (
|
{selectedProject ? (
|
||||||
isTracking ? (
|
isTracking ? (
|
||||||
<button
|
<button
|
||||||
|
|
@ -633,9 +655,9 @@ const DashboardLayout = () => {
|
||||||
{/* Layout Body */}
|
{/* Layout Body */}
|
||||||
<div className={`flex ${!isTracking ? "max-h-screen min-h-screen" : " h-[calc(100vh-3rem)]"} bg-base-200 overflow-hidden`}>
|
<div className={`flex ${!isTracking ? "max-h-screen min-h-screen" : " h-[calc(100vh-3rem)]"} bg-base-200 overflow-hidden`}>
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<aside className="w-20 bg-base-100 border-r border-base-300 flex-shrink-0 flex flex-col items-center py-4 space-y-4">
|
<aside className="w-20 bg-base-100 border-r border-base-300 flex flex-col items-center py-4 space-y-4">
|
||||||
{menuItems.map((item, index) => (
|
{menuItems.map((item , index) => (
|
||||||
<div className="tooltip z-100 tooltip-right" data-tip={item.label} key={index}>
|
<div className="tooltip z-100 tooltip-right" key={item.label} data-tip={item.label}>
|
||||||
<NavLink
|
<NavLink
|
||||||
key={item.label}
|
key={item.label}
|
||||||
to={item.path}
|
to={item.path}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { Clock, TrendingUp, Play, Square, ProjectorIcon, Pause } from "lucide-react";
|
import { Clock, TrendingUp, Play, Square, ProjectorIcon, Pause } from "lucide-react";
|
||||||
import { useOutletContext } from "react-router-dom";
|
import { useOutletContext } from "react-router-dom";
|
||||||
import NoData from "./NoData";
|
import NoData from "./NoData";
|
||||||
|
|
@ -92,6 +92,14 @@ const Dashboard: React.FC = () => {
|
||||||
projectsData,
|
projectsData,
|
||||||
} = useOutletContext<TrackingState>();
|
} = useOutletContext<TrackingState>();
|
||||||
|
|
||||||
|
// interface AppsListInt {
|
||||||
|
// name: string;
|
||||||
|
// pid: string;
|
||||||
|
// title: string;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const [runningApps, setRunningApps] = useState<AppsListInt[]>([]);
|
||||||
|
|
||||||
const selectedData = selectedProject
|
const selectedData = selectedProject
|
||||||
? projectTrackingMap.get(selectedProject.projectId)
|
? projectTrackingMap.get(selectedProject.projectId)
|
||||||
: null;
|
: null;
|
||||||
|
|
@ -207,10 +215,10 @@ const Dashboard: React.FC = () => {
|
||||||
<span className="text-base-content/70">Tracking:</span>{" "}
|
<span className="text-base-content/70">Tracking:</span>{" "}
|
||||||
<span
|
<span
|
||||||
className={`badge ${selectedData?.isPaused
|
className={`badge ${selectedData?.isPaused
|
||||||
? "badge-warning"
|
? "badge-warning"
|
||||||
: selectedData?.isTracking
|
: selectedData?.isTracking
|
||||||
? "badge-success"
|
? "badge-success"
|
||||||
: "badge-neutral"
|
: "badge-neutral"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{selectedData?.isPaused
|
{selectedData?.isPaused
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,8 @@ interface Window {
|
||||||
getActiveAppDetails: () => {};
|
getActiveAppDetails: () => {};
|
||||||
toggleCloseBtn: (shouldEnable: boolean) => {},
|
toggleCloseBtn: (shouldEnable: boolean) => {},
|
||||||
resizeWindow: (width: number, height: number) => {},
|
resizeWindow: (width: number, height: number) => {},
|
||||||
|
getRunningApps: () => {},
|
||||||
|
captureAppScreenShot: (appTitle: string) => Promise<string>,
|
||||||
onWindowUpdate: (callback: (data: any) => void) => void;
|
onWindowUpdate: (callback: (data: any) => void) => void;
|
||||||
getProjectList: () => Promise<ProjectListResponse>;
|
getProjectList: () => Promise<ProjectListResponse>;
|
||||||
startProjectTracking: (data: {
|
startProjectTracking: (data: {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue