refactor(desktop): separate js for neutralino from the js used on the website

This commit is contained in:
Julien Maille 2026-02-12 14:27:27 +01:00
parent 0213132606
commit cafa97cb0f
6 changed files with 79 additions and 78 deletions

View file

@ -4561,7 +4561,6 @@
</footer> </footer>
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/pocketbase@0.21.3/dist/pocketbase.umd.js"></script> <script src="https://cdn.jsdelivr.net/npm/pocketbase@0.21.3/dist/pocketbase.umd.js"></script>
<script src="/neutralino.js"></script>
<script type="module" src="/js/app.js"></script> <script type="module" src="/js/app.js"></script>
</body> </body>
</html> </html>

View file

@ -21,15 +21,8 @@ import { sidePanelManager } from './side-panel.js';
import { db } from './db.js'; import { db } from './db.js';
import { syncManager } from './accounts/pocketbase.js'; import { syncManager } from './accounts/pocketbase.js';
import { registerSW } from 'virtual:pwa-register'; import { registerSW } from 'virtual:pwa-register';
import { initializeDiscordRPC } from './discord-rpc.js';
import * as Neutralino from '@neutralinojs/lib';
import './smooth-scrolling.js'; import './smooth-scrolling.js';
// Assign Neutralino to window for global access
if (typeof window !== 'undefined' && window.NL_MODE) {
window.Neutralino = Neutralino;
}
import { initTracker } from './tracker.js'; import { initTracker } from './tracker.js';
// Lazy-loaded modules // Lazy-loaded modules
@ -238,46 +231,7 @@ async function disablePwaForAuthGate() {
} }
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
// Initialize desktop environment (Neutralino)
const urlParams = new URLSearchParams(window.location.search);
const hasNLParams = urlParams.has('NL_PORT') || urlParams.has('NL_TOKEN');
const isDesktop =
typeof window !== 'undefined' && (window.NL_MODE || window.location.port === '5050' || hasNLParams);
if (typeof window !== 'undefined') {
const nlGlobals = Object.keys(window).filter((k) => k.startsWith('NL_'));
console.log('[App] Environment Check:', {
isDesktop,
port: window.location.port,
hasNLParams,
nlGlobals,
});
}
if (typeof window !== 'undefined' && window.Neutralino) {
if (isDesktop) {
console.log('[App] Initializing Neutralino desktop environment...');
try {
Neutralino.init();
console.log('[App] Neutralino.init() called successfully.');
// Register events immediately
Neutralino.events.on('windowClose', () => {
console.log('[App] Window close event triggered.');
Neutralino.app.exit();
});
} catch (error) {
console.error('[App] Failed to initialize desktop environment:', error);
}
} else {
console.log('[App] Skipping Neutralino.init() on regular web environment.');
}
} else {
console.log('[App] Neutralino object NOT detected.');
}
const api = new MusicAPI(apiSettings); const api = new MusicAPI(apiSettings);
const audioPlayer = document.getElementById('audio-player'); const audioPlayer = document.getElementById('audio-player');
// i love ios and macos!!!! webkit fucking SUCKS BULLSHIT sorry ios/macos heads yall getting lossless only // i love ios and macos!!!! webkit fucking SUCKS BULLSHIT sorry ios/macos heads yall getting lossless only
@ -308,6 +262,17 @@ document.addEventListener('DOMContentLoaded', async () => {
const currentQuality = localStorage.getItem('playback-quality') || 'HI_RES_LOSSLESS'; const currentQuality = localStorage.getItem('playback-quality') || 'HI_RES_LOSSLESS';
const player = new Player(audioPlayer, api, currentQuality); const player = new Player(audioPlayer, api, currentQuality);
// Initialize tracker
initTracker(player);
// Initialize desktop features if in Neutralino mode
if (typeof window !== 'undefined' && (window.NL_MODE || window.location.search.includes('mode=neutralino'))) {
import('./desktop/desktop.js').then((m) => m.initDesktop(player));
}
const castBtn = document.getElementById('cast-btn');
initializeCasting(audioPlayer, castBtn);
const ui = new UIRenderer(api, player); const ui = new UIRenderer(api, player);
const scrobbler = new MultiScrobbler(); const scrobbler = new MultiScrobbler();
const lyricsManager = new LyricsManager(api); const lyricsManager = new LyricsManager(api);
@ -421,17 +386,6 @@ document.addEventListener('DOMContentLoaded', async () => {
initializeUIInteractions(player, api, ui); initializeUIInteractions(player, api, ui);
initializeKeyboardShortcuts(player, audioPlayer); initializeKeyboardShortcuts(player, audioPlayer);
// Initialize tracker
initTracker(player);
if (typeof window !== 'undefined' && window.Neutralino && isDesktop) {
console.log('[App] Starting Discord RPC...');
initializeDiscordRPC(player);
}
const castBtn = document.getElementById('cast-btn');
initializeCasting(audioPlayer, castBtn);
// Restore UI state for the current track (like button, theme) // Restore UI state for the current track (like button, theme)
if (player.currentTrack) { if (player.currentTrack) {
ui.setCurrentTrack(player.currentTrack); ui.setCurrentTrack(player.currentTrack);

22
js/desktop/desktop.js Normal file
View file

@ -0,0 +1,22 @@
// js/desktop/desktop.js
import Neutralino from './neutralino-bridge.js';
import { initializeDiscordRPC } from './discord-rpc.js';
export async function initDesktop(player) {
console.log('[Desktop] Initializing desktop features...');
// Assign to window for modules that use global Neutralino (like Player.js)
window.Neutralino = Neutralino;
try {
await Neutralino.init();
console.log('[Desktop] Neutralino initialized.');
if (player) {
console.log('[Desktop] Starting Discord RPC...');
initializeDiscordRPC(player);
}
} catch (error) {
console.error('[Desktop] Failed to initialize desktop environment:', error);
}
}

View file

@ -1,4 +1,5 @@
import { getTrackTitle, getTrackArtists } from './utils.js'; // js/desktop/discord-rpc.js
import { getTrackTitle, getTrackArtists } from '../utils.js';
export function initializeDiscordRPC(player) { export function initializeDiscordRPC(player) {
const EXTENSION_ID = 'js.neutralino.discordrpc'; const EXTENSION_ID = 'js.neutralino.discordrpc';

View file

@ -1,69 +1,94 @@
// js/neutralino-bridge.js // js/desktop/neutralino-bridge.js
const isNeutralino = typeof window !== 'undefined' && (
window.NL_MODE ||
window.location.search.includes('mode=neutralino') ||
(window.parent !== window)
);
const listeners = new Map(); const listeners = new Map();
// Listen for events from the Shell (Parent) // Listen for events from the Shell (Parent)
window.addEventListener('message', (event) => { if (isNeutralino) {
if (event.data?.type === 'NL_EVENT') { window.addEventListener('message', (event) => {
const { eventName, detail } = event.data; if (event.data?.type === 'NL_EVENT') {
if (listeners.has(eventName)) { const { eventName, detail } = event.data;
listeners.get(eventName).forEach((handler) => { if (listeners.has(eventName)) {
try { listeners.get(eventName).forEach((handler) => {
handler(detail); try {
} catch (e) { handler(detail);
console.error('[Bridge] Error in event handler:', e); } catch (e) {
} console.error('[Bridge] Error in event handler:', e);
}); }
});
}
} }
} });
}); }
export const init = async () => { export const init = async () => {
if (!isNeutralino) return;
// Notify Shell we are ready // Notify Shell we are ready
window.parent.postMessage({ type: 'NL_INIT' }, '*'); window.parent.postMessage({ type: 'NL_INIT' }, '*');
}; };
export const events = { export const events = {
on: (eventName, handler) => { on: (eventName, handler) => {
if (!isNeutralino) return;
if (!listeners.has(eventName)) { if (!listeners.has(eventName)) {
listeners.set(eventName, []); listeners.set(eventName, []);
} }
listeners.get(eventName).push(handler); listeners.get(eventName).push(handler);
}, },
off: (eventName, handler) => { off: (eventName, handler) => {
if (!isNeutralino) return;
if (!listeners.has(eventName)) return; if (!listeners.has(eventName)) return;
const handlers = listeners.get(eventName); const handlers = listeners.get(eventName);
const index = handlers.indexOf(handler); const index = handlers.indexOf(handler);
if (index > -1) handlers.splice(index, 1); if (index > -1) handlers.splice(index, 1);
}, },
broadcast: async (eventName, data) => { broadcast: async (eventName, data) => {
if (!isNeutralino) return;
window.parent.postMessage({ type: 'NL_BROADCAST', eventName, data }, '*'); window.parent.postMessage({ type: 'NL_BROADCAST', eventName, data }, '*');
}, },
}; };
export const extensions = { export const extensions = {
dispatch: async (extensionId, eventName, data) => { dispatch: async (extensionId, eventName, data) => {
if (!isNeutralino) return;
window.parent.postMessage({ type: 'NL_EXTENSION', extensionId, eventName, data }, '*'); window.parent.postMessage({ type: 'NL_EXTENSION', extensionId, eventName, data }, '*');
}, },
}; };
export const app = { export const app = {
exit: async () => { exit: async () => {
if (!isNeutralino) return;
window.parent.postMessage({ type: 'NL_APP_EXIT' }, '*'); window.parent.postMessage({ type: 'NL_APP_EXIT' }, '*');
}, },
}; };
const _window = { export const _window = {
minimize: async () => { minimize: async () => {
if (!isNeutralino) return;
window.parent.postMessage({ type: 'NL_WINDOW_MIN' }, '*'); window.parent.postMessage({ type: 'NL_WINDOW_MIN' }, '*');
}, },
maximize: async () => { maximize: async () => {
if (!isNeutralino) return;
window.parent.postMessage({ type: 'NL_WINDOW_MAX' }, '*'); window.parent.postMessage({ type: 'NL_WINDOW_MAX' }, '*');
}, },
show: async () => {
if (!isNeutralino) return;
window.parent.postMessage({ type: 'NL_WINDOW_SHOW' }, '*');
},
hide: async () => {
if (!isNeutralino) return;
window.parent.postMessage({ type: 'NL_WINDOW_HIDE' }, '*');
},
isVisible: async () => { isVisible: async () => {
return true; // Mock response return true; // Mock response
}, },
setTitle: async (title) => { setTitle: async (title) => {
if (!isNeutralino) return;
window.parent.postMessage({ type: 'NL_WINDOW_SET_TITLE', title }, '*'); window.parent.postMessage({ type: 'NL_WINDOW_SET_TITLE', title }, '*');
}, },
}; };

View file

@ -1,9 +1,9 @@
{ {
"applicationId": "com.monochrome.app", "applicationId": "com.monochrome.app",
"applicationName": "Monochrome", "applicationName": "Monochrome",
"applicationIcon": "public/assets/512.png", "applicationIcon": "public/assets/appicon.png",
"author": "Monochrome", "author": "Monochrome",
"description": "Lossless music streaming", "description": "Monochrome - Lossless music streaming",
"version": "1.0.0", "version": "1.0.0",
"defaultMode": "window", "defaultMode": "window",
"documentRoot": "dist/", "documentRoot": "dist/",
@ -15,7 +15,7 @@
"modes": { "modes": {
"window": { "window": {
"title": "Monochrome", "title": "Monochrome",
"icon": "public/assets/512.png", "icon": "public/assets/appicon.png",
"width": 1280, "width": 1280,
"height": 800, "height": 800,
"minWidth": 800, "minWidth": 800,
@ -44,5 +44,5 @@
"commandWindows": "powershell.exe -ExecutionPolicy Bypass -File \"${NL_PATH}/extensions/js.neutralino.discordrpc/bridge.ps1\"" "commandWindows": "powershell.exe -ExecutionPolicy Bypass -File \"${NL_PATH}/extensions/js.neutralino.discordrpc/bridge.ps1\""
} }
], ],
"nativeAllowList": ["app.exit", "window.*", "extensions.*", "events.*"] "nativeAllowList": ["app.exit", "window.*", "extensions.*", "events.*", "os.*"]
} }