Skip to content

Commit edbf331

Browse files
authored
Merge pull request #19 from topluyo/notification-and-package
Notification and package
2 parents 0424a1f + e8b0205 commit edbf331

22 files changed

Lines changed: 704 additions & 617 deletions

NotificationManager.js

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
const { BrowserWindow, screen, Notification, app } = require('electron');
2+
const path = require('path');
3+
4+
class NotificationManager {
5+
constructor() {
6+
this.queue = [];
7+
this.activeNotifications = []; // { id, win, timeout }
8+
this.maxVisible = 3;
9+
this.width = 320;
10+
this.height = 100;
11+
this.margin = 0;
12+
this.autoCloseMs = 30000; // 30 seconds auto-close
13+
}
14+
15+
enqueueOS(nativeOpts) {
16+
if (nativeOpts && Notification.isSupported()) {
17+
const notificationParams = {
18+
title: nativeOpts.title || 'Topluyo',
19+
body: nativeOpts.body || '',
20+
};
21+
if (nativeOpts.icon) {
22+
notificationParams.icon = nativeOpts.icon;
23+
} else {
24+
notificationParams.icon = path.join(app.getAppPath(), "topluyo.png");
25+
}
26+
27+
const nativeNotif = new Notification(notificationParams);
28+
nativeNotif.show();
29+
}
30+
}
31+
32+
enqueue(iframeUrl) {
33+
if (!iframeUrl) return;
34+
35+
const id = Date.now().toString() + Math.random().toString(36).substr(2, 5);
36+
this.queue.push({ id, iframeUrl });
37+
this.processQueue();
38+
}
39+
40+
processQueue() {
41+
if (this.queue.length === 0 || this.activeNotifications.length >= this.maxVisible) {
42+
return;
43+
}
44+
45+
const item = this.queue.shift();
46+
this.showNotification(item);
47+
}
48+
49+
animateMove(win, startY, targetY) {
50+
if (!win || win.isDestroyed()) return;
51+
const steps = 20;
52+
const stepDuration = 10; // ~200ms total
53+
const diff = targetY - startY;
54+
let currentStep = 0;
55+
56+
if (win.animationInterval) {
57+
clearInterval(win.animationInterval);
58+
}
59+
60+
win.animationInterval = setInterval(() => {
61+
if (!win || win.isDestroyed()) {
62+
clearInterval(win.animationInterval);
63+
return;
64+
}
65+
currentStep++;
66+
// Simple ease-out quadratic
67+
const t = currentStep / steps;
68+
const easeOut = t * (2 - t);
69+
const currentY = Math.round(startY + (diff * easeOut));
70+
const [x, _] = win.getPosition();
71+
win.setPosition(x, currentY);
72+
73+
if (currentStep >= steps) {
74+
clearInterval(win.animationInterval);
75+
win.animationInterval = null;
76+
}
77+
}, stepDuration);
78+
}
79+
80+
showNotification(item) {
81+
const primaryDisplay = screen.getPrimaryDisplay();
82+
const { width, height } = primaryDisplay.workAreaSize;
83+
const { x, y } = primaryDisplay.workArea;
84+
85+
const index = this.activeNotifications.length;
86+
const winX = x + width - this.width - this.margin;
87+
const targetY = y + height - ((index + 1) * (this.height + this.margin));
88+
const startY = targetY + this.height + this.margin; // Start slightly below target
89+
90+
const win = new BrowserWindow({
91+
x: winX,
92+
y: startY,
93+
width: this.width,
94+
height: this.height,
95+
frame: false,
96+
transparent: true,
97+
alwaysOnTop: true,
98+
skipTaskbar: true,
99+
resizable: false,
100+
hasShadow: false,
101+
show: false, // Don't show until ready to slide in
102+
webPreferences: {
103+
nodeIntegration: true,
104+
contextIsolation: false,
105+
preload: path.join(__dirname, 'preloads', 'notification.js')
106+
}
107+
});
108+
109+
win.loadURL(item.iframeUrl);
110+
111+
win.once('ready-to-show', () => {
112+
if (!win || win.isDestroyed()) return;
113+
win.showInactive();
114+
this.animateMove(win, startY, targetY);
115+
});
116+
117+
// We MUST cache the id because win.webContents will be destroyed on close
118+
const winId = win.webContents.id;
119+
120+
const timeout = setTimeout(() => {
121+
this.close(winId);
122+
}, this.autoCloseMs);
123+
124+
this.activeNotifications.push({
125+
id: winId,
126+
customId: item.id,
127+
win,
128+
timeout
129+
});
130+
131+
win.on('closed', () => {
132+
this.activeNotifications = this.activeNotifications.filter(n => n.id !== winId);
133+
this.recalculatePositions();
134+
this.processQueue();
135+
});
136+
}
137+
138+
recalculatePositions() {
139+
const primaryDisplay = screen.getPrimaryDisplay();
140+
const { width, height } = primaryDisplay.workAreaSize;
141+
const { x, y } = primaryDisplay.workArea;
142+
143+
const winX = x + width - this.width - this.margin;
144+
145+
this.activeNotifications.forEach((notification, index) => {
146+
if (notification.win && !notification.win.isDestroyed()) {
147+
const targetY = y + height - ((index + 1) * (this.height + this.margin));
148+
const [currentX, currentY] = notification.win.getPosition();
149+
if (currentY !== targetY) {
150+
this.animateMove(notification.win, currentY, targetY);
151+
}
152+
}
153+
});
154+
}
155+
156+
close(webContentsId) {
157+
const notification = this.activeNotifications.find(n => n.id === webContentsId);
158+
if (notification) {
159+
if (notification.timeout) clearTimeout(notification.timeout);
160+
if (notification.win && !notification.win.isDestroyed()) {
161+
// Option to slide out before closing
162+
const [x, currentY] = notification.win.getPosition();
163+
const targetY = currentY + this.height + this.margin;
164+
this.animateMove(notification.win, currentY, targetY);
165+
166+
// Wait for animation to finish before destroying
167+
setTimeout(() => {
168+
if (!notification.win.isDestroyed()) {
169+
notification.win.close();
170+
}
171+
}, 200);
172+
}
173+
}
174+
}
175+
176+
closeAll() {
177+
this.queue = [];
178+
const notifs = [...this.activeNotifications];
179+
notifs.forEach(n => {
180+
if (n.timeout) clearTimeout(n.timeout);
181+
if (n.win && !n.win.isDestroyed()) {
182+
n.win.close();
183+
}
184+
});
185+
this.activeNotifications = [];
186+
}
187+
}
188+
189+
module.exports = new NotificationManager();

Windows.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { BrowserWindow, session } = require("electron");
1+
const { BrowserWindow, session, app } = require("electron");
22
const path = require("path");
33
const log = require("electron-log");
44
const { mediaHandler } = require("./utils");
@@ -29,11 +29,11 @@ function createMainWindow(windowstate, url) {
2929
backgroundColor: "#ffffff",
3030
icon: path.join(__dirname, "topluyo.png"),
3131
webPreferences: {
32-
devTools: false,
32+
devTools: process.env.NODE_ENV === "development",
3333
contextIsolation: false,
3434
nodeIntegration: true,
3535
nodeIntegrationInSubFrames: true,
36-
preload: path.join(__dirname, "preload.js"),
36+
preload: path.join(__dirname, "preloads/main.js"),
3737
},
3838
});
3939

@@ -83,9 +83,14 @@ function createMainWindow(windowstate, url) {
8383
}
8484

8585
function checkForUpdatesAndLoad(mainWindow) {
86-
// Store versiyonunda auto-updater mevcut değilse direkt ana sayfayı yükle
87-
if (!autoUpdater || isWindowsStore) {
88-
console.log("Auto-updater not available in Store version, loading main page");
86+
// Desteklenmeyen ortamlarda (Store, development/paketlenmemiş veya AppImage olmayan Linux) güncellemeyi atla
87+
if (
88+
!autoUpdater ||
89+
isWindowsStore ||
90+
!app.isPackaged ||
91+
(process.platform === "linux" && !process.env.APPIMAGE)
92+
) {
93+
console.log("Auto-updater not supported in this environment, loading main page...");
8994
mainWindow.loadURL("https://topluyo.com");
9095
return;
9196
}
@@ -94,9 +99,6 @@ function checkForUpdatesAndLoad(mainWindow) {
9499
autoUpdater.logger.transports.file.level = "info";
95100
autoUpdater.autoDownload = true;
96101
autoUpdater.autoInstallOnAppQuit = false;
97-
if(process.env.NODE_ENV === "development") {
98-
mainWindow.loadURL("https://topluyo.com");
99-
}
100102
autoUpdater.on("checking-for-update", () => {
101103
console.log("Güncellemeler kontrol ediliyor...");
102104
autoUpdater.logger = log;

electron-builder.config.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@ const config = {
77
"files": [
88
"build/**/*",
99
"node_modules/**/*",
10-
"native/topluyo-capture/build/Release/*.node",
10+
"!node_modules/uiohook-napi/**/*",
11+
"node_modules/electron-native-screenshare/build/Release/*.node",
1112
"*.html",
1213
"icons/*",
1314
"*.rtf",
14-
"*.js"
15+
"*.js",
16+
"preloads/*.js"
1517
],
1618
"asarUnpack": [
17-
"node_modules/topluyo-capture/build/Release/*.node",
18-
"native/topluyo-capture/build/Release/*.node"
19+
"node_modules/electron-native-screenshare/build/Release/*.node"
1920
],
2021
"protocols": [
2122
{

linuxscript.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,20 @@ function registerProtocol() {
4343
const desktopPath = path.join(os.homedir(), ".local/share/applications/topluyo.desktop");
4444
const appPath = process.env.APPIMAGE || process.execPath;
4545

46+
const iconPath = process.env.APPIMAGE
47+
? path.join(process.env.APPDIR, ".DirIcon")
48+
: path.join(__dirname, "topluyo.png");
49+
4650
const desktopEntry = `[Desktop Entry]
4751
Name=Topluyo
48-
Exec=sh -c '"${appPath}" --no-sandbox %u'
52+
Exec=env ELECTRON_DISABLE_SANDBOX=1 sh -c '"${appPath}" --no-sandbox --disable-dev-shm-usage %u'
4953
Type=Application
5054
Terminal=false
5155
MimeType=x-scheme-handler/topluyo;
5256
Categories=Network;Chat;
5357
NoDisplay=false
5458
StartupWMClass=Topluyo
59+
Icon=${iconPath}
5560
`;
5661

5762
try {
@@ -66,7 +71,7 @@ StartupWMClass=Topluyo
6671
}
6772

6873
if (process.platform === "linux") {
69-
fixChromeSandbox();
74+
// fixChromeSandbox(); // Not needed when using --no-sandbox
7075
registerProtocol();
71-
ensureShmExists();
76+
// ensureShmExists(); // Not needed when using --disable-dev-shm-usage
7277
}

0 commit comments

Comments
 (0)