Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 177 additions & 73 deletions src/apps/webamp/webamp-app.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
import { Application } from '../../system/application.js';
import {
createTaskbarButton,
removeTaskbarButton,
updateTaskbarButton,
} from '../../shell/taskbar/taskbar.js';
import { ICONS } from '../../config/icons.js';
import { appManager } from '../../system/app-manager.js';
import { getWebampMenuItems } from './webamp.js';
import { isZenFSPath, getZenFSFileUrl, getZenFSFileAsText } from '../../system/zenfs-utils.js';

let webampInstance = null;
let webampContainer = null;
let webampTaskbarButton = null;
let isMinimized = false;

export class WebampApp extends Application {
static config = {
id: "webamp",
title: "Winamp",
description: "A classic music player.",
icon: ICONS.webamp, category: "",
icon: ICONS.webamp,
category: "",
hasTaskbarButton: true,
isSingleton: true,
tray: {
Expand All @@ -35,17 +29,109 @@ export class WebampApp extends Application {
super(config);
this.hasTaskbarButton = true;
this.blobUrls = [];
this.webampInstance = null;
}

_revokeBlobUrls() {
this.blobUrls.forEach((url) => URL.revokeObjectURL(url));
this.blobUrls = [];
}

_getWindowId(filePath) {
return "webamp-window";
}

_createWindow() {
// Webamp doesn't use a standard OS-GUI window, it renders directly to the body.
// We manage its container and lifecycle here.
return null; // Return null to prevent default window creation.
if (!webampContainer) {
webampContainer = document.createElement("div");
webampContainer.id = "webamp-window";
webampContainer.classList.add("app-window");
webampContainer.style.position = "absolute";
webampContainer.style.zIndex = window.System
? window.System.incrementZIndex()
: $Window.Z_INDEX++;

const screen =
document.getElementById("desktop-area") ||
document.getElementById("screen") ||
document.body;
screen.appendChild(webampContainer);

webampContainer.addEventListener(
"mousedown",
() => {
if (this.win) this.win.focus();
},
true,
);
}

const shim = {
element: webampContainer,
onClosed: (cb) => {
this._onClosedCallback = cb;
return () => {
this._onClosedCallback = null;
};
},
onFocus: (cb) => {
this._onFocusCallback = cb;
return () => {
this._onFocusCallback = null;
};
},
onBlur: (cb) => {
this._onBlurCallback = cb;
return () => {
this._onBlurCallback = null;
};
},
center: () => {
webampContainer.style.left = "calc(50% - 137px)";
webampContainer.style.top = "calc(50% - 58px)";
},
focus: () => {
this.bringToFront();
webampContainer.classList.add("focused");
if (this._onFocusCallback) this._onFocusCallback();
},
blur: () => {
webampContainer.classList.remove("focused");
if (this._onBlurCallback) this._onBlurCallback();
},
bringToFront: () => {
if (window.System) {
webampContainer.style.zIndex = window.System.incrementZIndex();
} else {
webampContainer.style.zIndex = $Window.Z_INDEX++;
}
},
minimize: () => {
this.minimizeWebamp();
},
unminimize: () => {
this.showWebamp();
},
restore: () => {
this.showWebamp();
},
close: () => {
appManager.closeApp(this.id);
},
setTitlebarIconSize: () => {},
getTitlebarIconSize: () => 16,
icons: this.config.icon,
title: (t) => {
if (t === undefined) return this.config.title;
return shim;
},
setMinimizeTarget: (target) => {
this._minimizeTarget = target;
},
};

webampContainer.$window = shim;
return shim;
}

async _onLaunch(filePath) {
Expand Down Expand Up @@ -142,22 +228,6 @@ export class WebampApp extends Application {
}

return new Promise((resolve, reject) => {
webampContainer = document.createElement("div");
webampContainer.id = "webamp-container";
webampContainer.style.position = "absolute";
webampContainer.style.zIndex = $Window.Z_INDEX++;
webampContainer.style.left = "50px";
webampContainer.style.top = "50px";
document.body.appendChild(webampContainer);

webampContainer.addEventListener(
"mousedown",
() => {
webampContainer.style.zIndex = $Window.Z_INDEX++;
},
true,
);

const initialTracks = [
{
metaData: {
Expand Down Expand Up @@ -189,14 +259,53 @@ export class WebampApp extends Application {
],
initialTracks,
});
this.webampInstance = webampInstance;

webampInstance.onMinimize(() => this.minimizeWebamp());
webampInstance.onClose(() => appManager.closeApp(this.id));
webampInstance.onMinimize(() => {
if (this._isHandlingWebampState) return;
if (this.win) this.win.minimize();
});
webampInstance.onClose(() => {
if (this.win) this.win.close();
});

// Move the webamp element into our container once it's rendered
// Webamp version 2.x often ignores the container passed to renderWhenReady
// and instead uses its own div.
webampInstance
.renderWhenReady(webampContainer)
.then(() => {
this.setupTaskbarButton();
// Ensure the webamp element is inside our container
const actualWebamp = document.getElementById("webamp");
if (
actualWebamp &&
actualWebamp !== webampContainer &&
actualWebamp.parentElement !== webampContainer
) {
webampContainer.appendChild(actualWebamp);
}
// Center the window after rendering
if (this.win && this.win.center) {
this.win.center();
}

// Ensure internal Winamp windows are correctly positioned within our container
const mainWin = actualWebamp.querySelector("#main-window");
if (mainWin) {
mainWin.style.left = "0px";
mainWin.style.top = "0px";
}
const eqWin = actualWebamp.querySelector("#equalizer-window");
if (eqWin) {
eqWin.style.left = "0px";
eqWin.style.top = "116px";
}
const plWin = actualWebamp.querySelector("#playlist-window");
if (plWin) {
plWin.style.left = "0px";
plWin.style.top = "232px";
}

this.showWebamp();
handleFile(filePath);
resolve(); // Resolve the promise once Webamp is ready
Expand All @@ -207,54 +316,54 @@ export class WebampApp extends Application {
});
}

setupTaskbarButton() {
const taskbarButtonId = "webamp-taskbar-button";
webampTaskbarButton = createTaskbarButton(
taskbarButtonId,
ICONS.webamp,
"Winamp",
);

if (webampTaskbarButton) {
webampTaskbarButton.addEventListener("click", (event) => {
event.preventDefault();
event.stopPropagation();
if (isMinimized) {
this.showWebamp();
} else {
this.minimizeWebamp();
}
});
showWebamp() {
if (webampContainer) {
webampContainer.style.display = "block";
webampContainer.style.visibility = "visible";
webampContainer.isMinimized = false;
webampContainer.classList.add("focused");
}
}
this.isMinimized = false;

showWebamp() {
const webampElement = document.getElementById("webamp");
if (!webampElement) return;

webampElement.style.display = "block";
webampElement.style.visibility = "visible";
isMinimized = false;
webampContainer.style.zIndex = $Window.Z_INDEX++;
if (webampTaskbarButton) {
updateTaskbarButton("webamp-taskbar-button", true, false);
if (this.webampInstance && !this._isHandlingWebampState) {
this._isHandlingWebampState = true;
this.webampInstance.restore();
this._isHandlingWebampState = false;
}

this.bringToFront();
if (this._onFocusCallback) this._onFocusCallback();
}

minimizeWebamp() {
const webampElement = document.getElementById("webamp");
if (!webampElement) return;

webampElement.style.display = "none";
webampElement.style.visibility = "hidden";
isMinimized = true;
if (webampTaskbarButton) {
updateTaskbarButton("webamp-taskbar-button", false, true);
if (webampContainer) {
webampContainer.style.display = "none";
webampContainer.style.visibility = "hidden";
webampContainer.isMinimized = true;
webampContainer.classList.remove("focused");
}
this.isMinimized = true;

if (this.webampInstance && !this._isHandlingWebampState) {
this._isHandlingWebampState = true;
this.webampInstance.minimize();
this._isHandlingWebampState = false;
}

if (this._onBlurCallback) this._onBlurCallback();
}

bringToFront() {
if (this.win && this.win.bringToFront) {
this.win.bringToFront();
}
}

_cleanup() {
this._revokeBlobUrls();
if (this._onClosedCallback) {
this._onClosedCallback();
}
if (webampContainer) {
webampContainer.remove();
webampContainer = null;
Expand All @@ -264,11 +373,6 @@ export class WebampApp extends Application {
webampInstance.dispose();
webampInstance = null;
}

if (webampTaskbarButton) {
removeTaskbarButton("webamp-taskbar-button");
webampTaskbarButton = null;
}
isMinimized = false;
this.isMinimized = false;
}
}