From 8902835191845a557bda88e97c6b2acb264d7092 Mon Sep 17 00:00:00 2001 From: Fathony L Date: Wed, 22 May 2024 18:48:45 +0700 Subject: [PATCH 1/2] merge from latest: x2nie-dev series: 12. --- packages/webamp-modern/src/UIRoot.ts | 231 +++++++++++----- packages/webamp-modern/src/css/button.css | 11 +- packages/webamp-modern/src/css/demo.css | 2 +- packages/webamp-modern/src/css/elements.css | 37 ++- .../webamp-modern/src/maki/interpreter.ts | 8 +- packages/webamp-modern/src/skin/Bitmap.ts | 2 +- .../webamp-modern/src/skin/SkinEngine_WAL.ts | 2 +- packages/webamp-modern/src/skin/VM.ts | 13 +- .../src/skin/makiClasses/GuiObj.ts | 24 ++ .../src/skin/makiClasses/Layout.ts | 80 +++++- .../src/skin/makiClasses/Menu.ts | 151 +++++++++-- .../src/skin/makiClasses/MenuItem.ts | 193 ++++++++++++++ .../src/skin/makiClasses/Movable.ts | 37 +-- .../src/skin/makiClasses/PopupMenu.ts | 246 +++++++++--------- .../src/skin/makiClasses/Text.ts | 41 ++- .../src/skin/makiClasses/menuWa5.ts | 128 +++++++-- .../src/skin/makiClasses/menuWa5actions.ts | 92 +++++++ 17 files changed, 1016 insertions(+), 282 deletions(-) create mode 100644 packages/webamp-modern/src/skin/makiClasses/MenuItem.ts create mode 100644 packages/webamp-modern/src/skin/makiClasses/menuWa5actions.ts diff --git a/packages/webamp-modern/src/UIRoot.ts b/packages/webamp-modern/src/UIRoot.ts index aced5787da..3c23c293d2 100644 --- a/packages/webamp-modern/src/UIRoot.ts +++ b/packages/webamp-modern/src/UIRoot.ts @@ -28,10 +28,21 @@ import Config from "./skin/makiClasses/Config"; import WinampConfig from "./skin/makiClasses/WinampConfig"; import Avs from "./skin/makiClasses/Avs"; +import { getWa5Popup } from "./skin/makiClasses/menuWa5"; + import { SkinEngineClass } from "./skin/SkinEngine"; -import { FileExtractor } from "./skin/FileExtractor"; +import { FileExtractor, PathFileExtractor, ZipFileExtractor, } from "./skin/FileExtractor"; import Application from "./skin/makiClasses/Application"; +import { + getSkinEngineClass, + getSkinEngineClassByContent, + SkinEngine, +} from "./skin/SkinEngine"; + + +export type Skin = | string | {name: string, url: string}; + export class UIRoot { _id: string; _application: Application; @@ -40,6 +51,7 @@ export class UIRoot { _winampConfig: WinampConfig; _div: HTMLDivElement = document.createElement("div"); + _mousePos : {x:number,y:number} = {x:0, y:0} _imageManager: ImageManager; // Just a temporary place to stash things _bitmaps: { [id: string]: Bitmap } = {}; @@ -62,6 +74,8 @@ export class UIRoot { _xFades: GroupXFade[] = []; _input: HTMLInputElement = document.createElement("input"); _skinInfo: { [key: string]: string } = {}; + _skin: Skin = {name:'', url:''}; + _skins: Skin[] = [] _skinEngineClass: SkinEngineClass; _eventListener: Emitter = new Emitter(); _additionalCss: string[] = []; @@ -89,6 +103,7 @@ export class UIRoot { this._winampConfig = new WinampConfig(this); this.playlist = new PlEdit(this); // must be after _config. this.vm = new Vm(this); + this.setlistenMouseMove(true) } getId(): string { @@ -516,7 +531,7 @@ export class UIRoot { cssEl.textContent = cssRules.join("\n"); } - dispatch(action: string, param: string | null, actionTarget: string | null) { + dispatch(action: string, param?: string | null, actionTarget?: string | null) { switch (action.toLowerCase()) { case "play": this.audio.play(); @@ -556,12 +571,38 @@ export class UIRoot { case "close": this.closeContainer(); break; + + case "menu": + getWa5Popup(param, this).popatmouse() + break; + + case "controlmenu": + getWa5Popup('ControlMenu', this).popatmouse() + break; + case "sysmenu": + getWa5Popup('Main', this).popatmouse() + break; + case "pe_add": + getWa5Popup('Add', this).popatmouse() + break; + case "pe_rem": + getWa5Popup('Remove', this).popatmouse() + break; + case "pe_sel": + getWa5Popup('Select', this).popatmouse() + break; + case "pe_misc": + getWa5Popup('MiscOpt', this).popatmouse() + break; + case "pe_list": + getWa5Popup('Playlist', this).popatmouse() + break; default: assume(false, `Unknown global action: ${action}`); } } - getActionState(action: string, param: string, actionTarget: string): boolean { + getActionState(action: string, param: string, actionTarget: string=''): boolean { if (action != null) { switch (action.toLowerCase()) { case "eq_toggle": @@ -653,6 +694,17 @@ export class UIRoot { } } + setlistenMouseMove( listen: boolean ){ + const update = (e:MouseEvent) => { + this._mousePos = { + // https://stackoverflow.com/questions/6073505/what-is-the-difference-between-screenx-y-clientx-y-and-pagex-y + x : e.pageX, + y : e.pageY + } + } + window.document[`${listen?'add':'remove'}EventListener`]('mousemove', update); + } + //? Zip things ======================== /* because maki need to load a groupdef outside init() */ _zip: JSZip; @@ -700,69 +752,6 @@ export class UIRoot { return await this._fileExtractor.getFileAsBlob(filePath); } - // async getFileAsString(filePath: string): Promise { - // if (this._preferZip) { - // return await this.getFileAsStringZip(filePath); - // } else { - // return await this.getFileAsStringPath(filePath); - // } - // } - // async getFileAsBytes(filePath: string): Promise { - // if (this._preferZip) { - // return await this.getFileAsBytesZip(filePath); - // } else { - // return await this.getFileAsBytesPath(filePath); - // } - // } - // async getFileAsBlob(filePath: string): Promise { - // if (this._preferZip) { - // return await this.getFileAsBlobZip(filePath); - // } else { - // return await this.getFileAsBlobPath(filePath); - // } - // } - - // async getFileAsStringZip(filePath: string): Promise { - // if (!filePath) return null; - // const zipObj = getCaseInsensitiveFile(this._zip, filePath); - // if (!zipObj) return null; - // return await zipObj.async("text"); - // } - - // async getFileAsBytesZip(filePath: string): Promise { - // if (!filePath) return null; - // const zipObj = getCaseInsensitiveFile(this._zip, filePath); - // if (!zipObj) return null; - // return await zipObj.async("arraybuffer"); - // } - - // async getFileAsBlobZip(filePath: string): Promise { - // if (!filePath) return null; - // const zipObj = getCaseInsensitiveFile(this._zip, filePath); - // if (!zipObj) return null; - // return await zipObj.async("blob"); - // } - - // async getFileAsStringPath(filePath: string): Promise { - // const response = await fetch(this._skinPath + filePath); - // return await response.text(); - // } - - // async getFileAsBytesPath(filePath: string): Promise { - // const response = await fetch(this._skinPath + filePath); - // return await response.arrayBuffer(); - // } - - // async getFileAsBlobPath(filePath: string): Promise { - // const response = await fetch(this._skinPath + filePath); - // return await response.blob(); - // } - - // getFileIsExist(filePath: string): boolean { - // const zipObj = getCaseInsensitiveFile(this._zip, filePath); - // return !!zipObj; - // } - //? System things ======================== /* because maki need to be run if not inside any Group @init() */ addSystemObject(systemObj: SystemObject) { @@ -782,7 +771,14 @@ export class UIRoot { } setSkinInfo(skinInfo: { [key: string]: string }) { - this._skinInfo = skinInfo; + const url = this._skinInfo.url + this._skinInfo = {...skinInfo, url}; + } + setSkinUrl(url:string){ + this._skinInfo.url = url; + } + getSkinUrl(){ + return this._skinInfo.url } getSkinInfo(): { [key: string]: string } { return this._skinInfo; @@ -791,6 +787,107 @@ export class UIRoot { return this.getSkinInfo()["name"]; } + // THIS IS A BIG THING, MOVED HERE FROM AN AGNOSTIC SKIN ENGINES + async switchSkin(skin: Skin) { + //* getting skin engine is complicated: + //* SkinEngine is not yet instanciated during looking for a skinEngine. + //* If file extension is know then we loop for registered Engines + //* But sometime (if its a `.zip` or a path `/`), we need to detect by + //* if a file exist, with a name is expected by skinEngine + + const parentDiv = this.getRootDiv().parentElement; + this.reset(); + // this._parent.appendChild(this._uiRoot.getRootDiv()); + parentDiv.appendChild(this.getRootDiv()); + + const name = typeof skin === 'string' ? skin : skin.name; + const skinPath = typeof skin === 'string' ? skin : skin.url; + this.setSkinUrl(skinPath) + + + let skinFetched = false; + let SkinEngineClass = null; + + //? usually the file extension is explicitly for SkinEngine. eg: `.wal` + let SkinEngineClasses = await getSkinEngineClass(skinPath); + + //? when file extension is ambiguous eg. `.zip`, several + //? skinEngines are supporting, but only one is actually working with. + //? lets detect: + if (SkinEngineClasses.length > 1) { + await this._loadSkinPathToUiroot(skinPath, null); + skinFetched = true; + SkinEngineClass = await getSkinEngineClassByContent( + SkinEngineClasses, + skinPath, + this + ); + } else { + SkinEngineClass = SkinEngineClasses[0]; + } + if (SkinEngineClass == null) { + throw new Error(`Skin not supported`); + } + + //? success found a skin-engine + this.SkinEngineClass = SkinEngineClass; + const parser: SkinEngine = new SkinEngineClass(this); + if (!skinFetched) + await this._loadSkinPathToUiroot(skinPath, parser); + // await parser.parseSkin(); + await parser.buildUI(); + + // loadSkin(this._parent, skinPath); + } + + /** + * Time to load the skin file + * @param skinPath url string + * @param uiRoot + * @param skinEngine An instance of SkinEngine + */ + private async _loadSkinPathToUiroot( + skinPath: string, + skinEngine: SkinEngine + ) { + let response: Response; + let fileExtractor: FileExtractor; + //? pick one of correct fileExtractor + + if (skinPath.endsWith("/")) { + fileExtractor = new PathFileExtractor(); + } else { + response = await fetch(skinPath); + if (response.status == 404) { + throw new Error(`Skin does not exist`); + } + if (skinEngine != null) { + fileExtractor = skinEngine.getFileExtractor(); + } + } + if (fileExtractor == null) { + if (response.headers.get("content-type").startsWith("application/")) { + fileExtractor = new ZipFileExtractor(); + } else { + fileExtractor = new PathFileExtractor(); + } + } + + await fileExtractor.prepare(skinPath, response); + // const skinZipBlob = await response.blob(); + + // const zip = await JSZip.loadAsync(skinZipBlob); + // uiRoot.setZip(zip); + // } else { + // uiRoot.setZip(null); + // const slash = skinPath.endsWith("/") ? "" : "/"; + // uiRoot.setSkinDir(skinPath + slash); + // } + this.setFileExtractor(fileExtractor); + } + + + set SkinEngineClass(Engine: SkinEngineClass) { this._skinEngineClass = Engine; } diff --git a/packages/webamp-modern/src/css/button.css b/packages/webamp-modern/src/css/button.css index b0e91d5bf5..9b60ebf2f8 100644 --- a/packages/webamp-modern/src/css/button.css +++ b/packages/webamp-modern/src/css/button.css @@ -13,4 +13,13 @@ button.wasabi { button.wasabi:active { border-image-source: var(--bitmap-studio-button-pressed); -} \ No newline at end of file +} + +button.center_image::before { + content: ''; + position: absolute; + inset: 0; + background-image: var(--background-image); + background-repeat: no-repeat; + background-position: center center; +} diff --git a/packages/webamp-modern/src/css/demo.css b/packages/webamp-modern/src/css/demo.css index 5d5c2193ce..16285927b7 100644 --- a/packages/webamp-modern/src/css/demo.css +++ b/packages/webamp-modern/src/css/demo.css @@ -1,7 +1,7 @@ body { margin: 0; background-color: rgb(58, 110, 165); - background-image: url(img/wallpaper.png); + /* background-image: url(img/wallpaper.png); */ font-family: Arial, Helvetica, sans-serif; } #experimental { diff --git a/packages/webamp-modern/src/css/elements.css b/packages/webamp-modern/src/css/elements.css index 11dfd445db..c32c7f9f5e 100644 --- a/packages/webamp-modern/src/css/elements.css +++ b/packages/webamp-modern/src/css/elements.css @@ -114,6 +114,16 @@ text wrap { font-family: monospace; white-space: pre; } +text wrap { + margin-left: 2px; +} +text wrap[font="TrueType"] { + /* needed for titlebar */ + font-size: 10.5px; + line-height: 10px; + vertical-align: var(--valign, center); + text-align: var(--align, center); +} text wrap[font="BitmapFont"] { display: flex; white-space: nowrap; @@ -121,7 +131,6 @@ text wrap[font="BitmapFont"] { /* align-items: center; */ align-items: var(--valign, center); justify-content: var(--align, center); - margin-left: 2px; } text span { user-select: none; @@ -148,6 +157,11 @@ menu { padding: 0; list-style: none; } +.popup hr { + margin-block-start: 3px; + margin-block-end: 3px; + border-bottom: none; +} /* frame2 { box-shadow: inset 0 0 5px red; } */ @@ -264,6 +278,7 @@ container:not(:active):not(container:focus-within) .webamp--img.inactivable { } .resizing { + position:fixed; border: 1px solid blue; background-color: rgba(74, 74, 251, 0.205); z-index: 1000; @@ -311,6 +326,7 @@ menu > .popup{ width: auto; display: inline-block; box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); + font-size: 10.5px; } ul.popup-menu-container li { display: flex; @@ -322,7 +338,7 @@ ul.popup-menu-container li > span { padding: 4px 0; } ul.popup-menu-container li:hover > span { - background: blue; + background: #316ac5; color: white; } @@ -341,6 +357,7 @@ ul.popup-menu-container li:hover > span { min-width: 15px; text-align: center; font-size: smaller; + line-height: 1; } /* nested popup */ @@ -350,4 +367,18 @@ ul.popup-menu-container li:hover > span { } .popup-menu-container > li:hover > .popup-menu-container{ display: unset; -} \ No newline at end of file +} + +/* COMPONENT BUCKET */ +componentbucket[id="component list"] wrapper button { + width: 44px; + height: 34px; + /* border: none; */ + /* border: 2px solid fuchsia; */ + margin: 0 1px; + position: unset; + display: inline-block; + /* left: unset; */ + /* top: unset; */ + /* background: red; */ +} diff --git a/packages/webamp-modern/src/maki/interpreter.ts b/packages/webamp-modern/src/maki/interpreter.ts index 7f36ce7afe..9005d2db59 100644 --- a/packages/webamp-modern/src/maki/interpreter.ts +++ b/packages/webamp-modern/src/maki/interpreter.ts @@ -49,7 +49,13 @@ export async function interpret( uiRoot ); interpreter.stack = stack; - return await interpreter.interpret(start); + try { + return await interpreter.interpret(start); + // return interpreter.interpret(start); + } catch (error) { + // console.warn('error while interpret', program.file, error) + console.warn(`Stopped executing ${program.maki_id}.\n`, error); + } } function validateVariable(v: Variable) { diff --git a/packages/webamp-modern/src/skin/Bitmap.ts b/packages/webamp-modern/src/skin/Bitmap.ts index 8813ae2133..f5c9564ee0 100644 --- a/packages/webamp-modern/src/skin/Bitmap.ts +++ b/packages/webamp-modern/src/skin/Bitmap.ts @@ -191,7 +191,7 @@ export default class Bitmap { const groupId = this.getGammaGroup(); const gammaGroup = uiRoot._getGammaGroup(groupId); - if (gammaGroup._value == "0,0,0") { + if (gammaGroup._value == "0,0,0" && gammaGroup._gray == 0) { // triple zero meaning no gamma should be applied. // return bitmap.getCanvas().toDataURL(); const url = await this.toDataURL(uiRoot); diff --git a/packages/webamp-modern/src/skin/SkinEngine_WAL.ts b/packages/webamp-modern/src/skin/SkinEngine_WAL.ts index e0e14e500f..ce97f9ddc4 100644 --- a/packages/webamp-modern/src/skin/SkinEngine_WAL.ts +++ b/packages/webamp-modern/src/skin/SkinEngine_WAL.ts @@ -101,7 +101,7 @@ export default class SkinEngineWAL extends SkinEngine { async prepareArial() { const node: XmlElement = new XmlElement("truetypefont", { id: "Arial", - family: "Arial", + family: "Arial, 'Liberation Sans', 'DejaVu Sans'", }); await this.trueTypeFont(node, null); } diff --git a/packages/webamp-modern/src/skin/VM.ts b/packages/webamp-modern/src/skin/VM.ts index 89646a295b..9c7c272b4d 100644 --- a/packages/webamp-modern/src/skin/VM.ts +++ b/packages/webamp-modern/src/skin/VM.ts @@ -47,7 +47,11 @@ export default class Vm { // This could easily become performance sensitive. We could make this more // performant by normalizing some of these things when scripts are added. - async dispatch(object: BaseObject, event: string, args: Variable[] = []): number { + async dispatch( + object: BaseObject, + event: string, + args: Variable[] = [] + ): number { const reversedArgs = [...args].reverse(); let executed = 0; for (const script of this._scripts) { @@ -83,7 +87,12 @@ export default class Vm { } if (match) { - await this.interpret(script, binding.commandOffset, event, reversedArgs); + await this.interpret( + script, + binding.commandOffset, + event, + reversedArgs + ); // return 1; executed++; } diff --git a/packages/webamp-modern/src/skin/makiClasses/GuiObj.ts b/packages/webamp-modern/src/skin/makiClasses/GuiObj.ts index 16b8b998ea..6b177181cd 100644 --- a/packages/webamp-modern/src/skin/makiClasses/GuiObj.ts +++ b/packages/webamp-modern/src/skin/makiClasses/GuiObj.ts @@ -20,6 +20,30 @@ import { XmlElement } from "@rgrove/parse-xml"; let BRING_LEAST: number = -1; let BRING_MOST_TOP: number = 1; +const globalMouseDown:Function[] = []; +export const installGlobalMouseDown = (f:Function) => { + if(!globalMouseDown.includes(f)){ + globalMouseDown.push(f) + } +} +export const uninstallGlobalMouseDown = (f:Function) => { + const index = globalMouseDown.indexOf(f); + if(index!=-1){ + // delete globalMouseDown[index] + globalMouseDown.splice(index, 1) + } +} + +export const executeGlobalMouseDown = (e:MouseEvent) => { + for (let i = 0; i < globalMouseDown.length; i++) { + // console.log(typeof globalMouseDown[i], '>>', globalMouseDown[i]); + globalMouseDown[i](e); + } + // for (let mousedown of globalMouseDown) { + // mousedown(e) + // } +} + // http://wiki.winamp.com/wiki/XML_GUI_Objects#GuiObject_.28Global_params.29 export default class GuiObj extends XmlObj { static GUID = "4ee3e1994becc636bc78cd97b028869c"; diff --git a/packages/webamp-modern/src/skin/makiClasses/Layout.ts b/packages/webamp-modern/src/skin/makiClasses/Layout.ts index 505a58116c..348d643592 100644 --- a/packages/webamp-modern/src/skin/makiClasses/Layout.ts +++ b/packages/webamp-modern/src/skin/makiClasses/Layout.ts @@ -4,6 +4,9 @@ import Container from "./Container"; import { LEFT, RIGHT, TOP, BOTTOM, CURSOR, MOVE } from "../Cursor"; import { px, unimplemented } from "../../utils"; import { UIRoot } from "../../UIRoot"; +import PopupMenu from "./PopupMenu"; +import { forEachMenuItem, IMenuItem } from "./MenuItem"; +import { findAction } from "./menuWa5actions"; // > A layout is a special kind of group, which shown inside a container. Each // > layout represents an appearance for that window. Layouts give you the ability @@ -17,6 +20,7 @@ export default class Layout extends Group { static GUID = "60906d4e482e537e94cc04b072568861"; _resizingDiv: HTMLDivElement = null; _resizing: boolean = false; + _resizing_start : DOMRect = null; _canResize: number = 0; // combination of 4 directions: N/E/W/S _scale: number = 1.0; _opacity: number = 1.0; @@ -25,6 +29,7 @@ export default class Layout extends Group { _movingStartY: number; _moving: boolean = false; _snap = { left: 0, top: 0, right: 0, bottom: 0 }; + _shortcuts: {[key:string]:number} = {}; constructor(uiRoot: UIRoot) { super(uiRoot); @@ -178,21 +183,33 @@ export default class Layout extends Group { h = this._minimumHeight ? Math.max(h, this._minimumHeight) : h; return h; }; - const container = this._parent; - const r = this._div.getBoundingClientRect(); + // const container = this._parent; if (cmd == "constraint") { this._canResize = dx; } else if (cmd == "start") { this.bringtofront(); + const r = this._div.getBoundingClientRect(); + // r.x = container.getleft() + // r.y = container.gettop() + this._resizing_start = r; + // this._resizing_o = r; this._resizing = true; this._resizingDiv = document.createElement("div"); this._resizingDiv.className = "resizing"; - this._resizingDiv.style.cssText = "position:fixed;"; - this._resizingDiv.style.width = px(r.width); - this._resizingDiv.style.height = px(r.height); - this._resizingDiv.style.top = px(container.gettop()); - this._resizingDiv.style.left = px(container.getleft()); - this._div.appendChild(this._resizingDiv); + // this._resizingDiv.style.cssText = "position:fixed;"; + // this._resizingDiv.style.width = px(r.width); + // this._resizingDiv.style.height = px(r.height); + // this._resizingDiv.style.top = px(container.gettop()); + // this._resizingDiv.style.left = px(container.getleft()); + const {left,top,width,height} = r + this._resizingDiv.style.cssText = ` + width: ${px(width)}; + height: ${px(height)}; + left: ${px(left)}; + top: ${px(top)}; + `; + // this._div.appendChild(this._resizingDiv); + document.body.appendChild(this._resizingDiv); } else if (dx == CURSOR && dy == CURSOR) { this._resizingDiv.style.cursor = cmd; } else if (cmd == "move") { @@ -200,19 +217,37 @@ export default class Layout extends Group { return; } // console.log(`resizing dx:${dx} dy:${dy}`); + let {left,top,width,height, right, bottom} = this._resizing_start if (this._canResize & RIGHT) - this._resizingDiv.style.width = px(clampW(r.width + dx)); + width = clampW(width + dx); if (this._canResize & BOTTOM) - this._resizingDiv.style.height = px(clampH(r.height + dy)); + height = (clampH(height + dy)); if (this._canResize & LEFT) { - this._resizingDiv.style.left = px(container.getleft() + dx); - this._resizingDiv.style.width = px(clampW(r.width + -dx)); + width = (clampW(width + -dx)); + let l = (left + dx); + if(l+width <= right) { + left = l; + } else { + left = right - this._minimumWidth + } } if (this._canResize & TOP) { - this._resizingDiv.style.top = px(container.gettop() + dy); - this._resizingDiv.style.height = px(clampH(r.height + -dy)); + height = (clampH(height + -dy)); + let t = (top + dy); + if(t+height <= bottom) { + top = t; + } else { + top = bottom - this._minimumHeight + } } + this._resizingDiv.style.cssText = ` + width: ${px(width)}; + height: ${px(height)}; + left: ${px(left)}; + top: ${px(top)}; + `; + } else if (cmd == "final") { if (!this._resizing) { return; @@ -259,4 +294,21 @@ export default class Layout extends Group { this._moving = false; } } + + // MENU SHORTCUT HANDLER HERE ====================== + registerShortcuts(popup: PopupMenu){ + forEachMenuItem(popup, (m: IMenuItem) => { + if(m.shortcut){ + this._shortcuts[m.shortcut] = m.id + } + }) + // console.log('layout.shortcuts:', this._shortcuts) + } + + executeShorcut(shortcut:string){ + const menuId = this._shortcuts[shortcut] + const action = findAction(menuId); + // console.log('Layout:', this._name, 'executing shortcut:',shortcut, '@action.id:', menuId, '=:', action ) + const invalidateRequired = action.onExecute(this._uiRoot); + } } diff --git a/packages/webamp-modern/src/skin/makiClasses/Menu.ts b/packages/webamp-modern/src/skin/makiClasses/Menu.ts index 82839dc01a..e67f07d0e3 100644 --- a/packages/webamp-modern/src/skin/makiClasses/Menu.ts +++ b/packages/webamp-modern/src/skin/makiClasses/Menu.ts @@ -5,10 +5,41 @@ import Group from "./Group"; // import Button from "./Button"; import Layer from "./Layer"; import { getWa5Popup } from "./menuWa5"; -import { generatePopupDiv } from "./PopupMenu"; +import PopupMenu from "./PopupMenu"; +import { ICLoseablePopup, destroyActivePopup, generatePopupDiv, setActivePopup } from "./MenuItem"; +import { findAction, updateActions } from "./menuWa5actions"; +let ACTIVE_MENU_GROUP: string = '' +// let ACTIVE_MENU: Menu = null; + +/*function destroyActivePopup() { + console.log('globalWindowClick') + if (ACTIVE_MENU != null) { + ACTIVE_MENU.doCloseMenu() + } + ACTIVE_MENU_GROUP = '' +} + +let globalClickInstalled = false; +function installGlobalClickListener() { + setTimeout(() => { // using promise to prevent immediately executing of globalWindowClick + installGlobalMouseDown(destroyActivePopup); // call globalWindowClick on any GuiObj + if (!globalClickInstalled) { + document.addEventListener("mousedown", destroyActivePopup); // call globalWindowClick on document + globalClickInstalled = true; + } + }, 500); +} +function uninstallGlobalClickListener() { + if (globalClickInstalled) { + document.removeEventListener("mousedown", destroyActivePopup); + globalClickInstalled = false; + } + + uninstallGlobalMouseDown(destroyActivePopup) +}*/ // http://wiki.winamp.com/wiki/XML_GUI_Objects#? -export default class Menu extends Group { +export default class Menu extends Group implements ICLoseablePopup { static GUID = "73c00594401b961f24671b9b6541ac27"; //static GUID "73C00594-961F-401B-9B1B-672427AC4165"; _normalId: string; @@ -22,7 +53,8 @@ export default class Menu extends Group { _elHover: GuiObj; _elDown: GuiObj; _elImage: Layer; - _popup: HTMLElement; + _popup: PopupMenu; + _popupDiv: HTMLElement; setXmlAttr(_key: string, value: string): boolean { const key = _key.toLowerCase(); @@ -99,25 +131,69 @@ export default class Menu extends Group { } } } + + doClosePopup() { + this._showButton(this._elNormal); + this._div.classList.remove("open"); + // ACTIVE_MENU = null; + // document.removeEventListener("mousedown", globalWindowClick); + // uninstallGlobalMouseDown(globalWindowClick) + // uninstallGlobalClickListener() + // ACTIVE_MENU_GROUP = '' + } + onLeftButtonDown(x: number, y: number) { - super.onLeftButtonDown(x, y); - this._showButton(this._elDown); + // super.onLeftButtonDown(x, y); + // this._showButton(this._elDown); + //? toggle dropdown visibility + if (ACTIVE_MENU_GROUP != this._menuGroupId) { + ACTIVE_MENU_GROUP = this._menuGroupId; + destroyActivePopup() + // setTimeout(() => { + // installGlobalMouseDown(globalWindowClick); + // }, 500); + // installGlobalClickListener() + setActivePopup(this) + + } else { + ACTIVE_MENU_GROUP = null; + // if (ACTIVE_MENU != null) { + // ACTIVE_MENU.doCloseMenu() + // } + destroyActivePopup() + } + this.onEnterArea() } onEnterArea() { - super.onEnterArea(); - this._showButton(this._elHover); - this._div.classList.add("open"); + // super.onEnterArea(); + if (ACTIVE_MENU_GROUP == this._menuGroupId) { + // if (ACTIVE_MENU != null) { + // ACTIVE_MENU.doCloseMenu() + // } + destroyActivePopup() + this._showButton(this._elDown); + this._div.classList.add("open"); + + // ACTIVE_MENU = this; + setActivePopup(this) + } else { + this._showButton(this._elHover); + } } onLeaveArea() { - super.onLeaveArea(); - this._showButton(this._elNormal); - this._div.classList.remove("open"); + // super.onLeaveArea(); + if (ACTIVE_MENU_GROUP != this._menuGroupId) { + this._showButton(this._elNormal); + // this._div.classList.remove("open"); + } } - init() { - super.init(); + + setup() { + super.setup(); // this.resolveButtonsAction(); // this._uiRoot.vm.dispatch(this, "onstartup", []); + this.getparentlayout().registerShortcuts(this._popup) } resolveButtonsAction() { //console.log('found img') @@ -157,20 +233,47 @@ export default class Menu extends Group { // this._div.classList.remove("vertical"); // } - - if(this._menuId.startsWith('WA5:')){ - const [,popupId] = this._menuId.split(':') - const popupMenu = getWa5Popup(popupId) + + if (this._menuId.startsWith('WA5:')) { + const [, popupId] = this._menuId.split(':') + this._popup = getWa5Popup(popupId, this._uiRoot); + + this.invalidatePopup() // function menuClick(id:number){ // console.log('menu clicked:', id) // } - this._popup = generatePopupDiv(popupMenu, (id:number) => console.log('menu clicked:', id)) - } else { - this._popup = document.createElement("div"); - this._popup.classList.add("fake-popup"); } - this._popup.classList.add("popup"); - // this._appendChildrenToDiv(this._popup); - this._div.appendChild(this._popup); + } + + /** + * update the checkmark, enabled/disabled, etc + */ + invalidatePopup() { + const self = this; + if (this._popup) { + + // destroy old DOM + if(this._popupDiv){ + this._popupDiv.remove() + } + + updateActions(this._popup, this._uiRoot); // let winamp5 menus reflect the real config/condition + + const menuItemClick = (id: number) => { + console.log('menu clicked:', id); + const action = findAction(id); + const invalidateRequired = action.onExecute(self._uiRoot); + if(invalidateRequired) self.invalidatePopup(); + } + + this._popupDiv = generatePopupDiv(this._popup, menuItemClick); + // } else { + // this._popupDiv = document.createElement("div"); + // this._popupDiv.classList.add("fake-popup"); + // } + this._popupDiv.classList.add("popup"); + // this._appendChildrenToDiv(this._popup); + this._div.appendChild(this._popupDiv); + } } } diff --git a/packages/webamp-modern/src/skin/makiClasses/MenuItem.ts b/packages/webamp-modern/src/skin/makiClasses/MenuItem.ts new file mode 100644 index 0000000000..47afce46f2 --- /dev/null +++ b/packages/webamp-modern/src/skin/makiClasses/MenuItem.ts @@ -0,0 +1,193 @@ +import { installGlobalMouseDown, uninstallGlobalMouseDown } from "./GuiObj"; + +export interface IPopupMenu { + children: MenuItem[]; +} + +export type IMenuItem = { + type: "menuitem"; + caption: string; + id: number; + checked: boolean; + disabled?: boolean; + shortcut?: string; // "Ctrl+Alt+Shift+A" + keychar?: string; // 'p' of "&Play" + invisible?: boolean;// special case to register shortcut only + data?:{[key:string]: any}; // used by skin's popup item +} + +type IMenuSeparator = { + type: "separator" +} + +type IMenuPopup = { + type: "popup"; + caption: string; + popup: IPopupMenu; + checked?: boolean; + disabled?: boolean; + children?: MenuItem[]; +}; + +export type MenuItem = | IMenuItem | IMenuSeparator | IMenuPopup; + +export interface ICLoseablePopup { + doClosePopup: Function; +} + +// ################## Popup Utils ##########################33 + +let ACTIVE_POPUP: ICLoseablePopup = null; + +export function destroyActivePopup() { + console.log('globalWindowClick') + if (ACTIVE_POPUP != null) { + ACTIVE_POPUP.doClosePopup() + } + // ACTIVE_MENU_GROUP = '' + uninstallGlobalClickListener() +} + +export function setActivePopup(popup: ICLoseablePopup) { + ACTIVE_POPUP = popup; + if(popup){ + installGlobalClickListener() + } else { + uninstallGlobalClickListener() + } +} + +/** + * if the active == popup => set null + * @param popup + */ +export function deactivePopup(popup: ICLoseablePopup) { + if(popup == ACTIVE_POPUP){ + setActivePopup(null) + } +} + +let globalClickInstalled = false; +function installGlobalClickListener() { + setTimeout(() => { // using promise to prevent immediately executing of globalWindowClick + if (!globalClickInstalled) { + installGlobalMouseDown(destroyActivePopup); // call globalWindowClick on any GuiObj + document.addEventListener("mousedown", destroyActivePopup); // call globalWindowClick on document + globalClickInstalled = true; + } + }, 500); +} + +function uninstallGlobalClickListener() { + if (globalClickInstalled) { + document.removeEventListener("mousedown", destroyActivePopup); + globalClickInstalled = false; + uninstallGlobalMouseDown(destroyActivePopup) + } + +} + +// ################## MenuItem Utils ##########################33 + +export function forEachMenuItem(popup: IPopupMenu, callback:Function){ + for(const menu of popup.children){ + if(menu.type=="menuitem"){ + callback(menu) + } + else if(menu.type=="popup"){ + forEachMenuItem(menu.popup, callback) + } + } +} + +type ExtractedCaptions = { + caption: string; + shortcut?: string; // "Ctrl+Alt+Shift+A" + keychar?: string; // 'p' of "&Play" +} + +export function extractCaption(text:string): ExtractedCaptions{ + // const result:ExtractedCaptions = {caption:text, shortcut:null, keychar:''} + const [caption, shortcut] = text.split('\t') + const keychar = caption.includes('&')? caption[caption.indexOf('&')+1].toLowerCase() : '' + return {caption, shortcut, keychar}; +} + +export function generatePopupDiv(popup: IPopupMenu, callback: Function): HTMLElement { + const root = document.createElement("ul"); + root.className = 'popup-menu-container' + // root.style.zIndex = "1000"; + // console.log('generating popup:', popup) + for (const menu of popup.children) { + // const menuitem = document.createElement("li"); + let item: HTMLElement; + // root.appendChild(item); + switch (menu.type) { + case "menuitem": + if(menu.invisible===true){ + continue; + } + item = generatePopupItem(menu); + item.addEventListener("mousedown", (e) => callback(menu.id)); + // item.onclick = (e) => callback(menu.id); + break; + case "popup": + item = generatePopupItem(menu); + const subMenu = generatePopupDiv(menu.popup, callback); + item.appendChild(subMenu) + break; + case "separator": + item = document.createElement("hr"); + break; + } + root.appendChild(item); + } + return root; +} + +//? one row of popup +function generatePopupItem(menu: | IMenuItem | IMenuPopup): HTMLElement { + const item = document.createElement("li"); + + //? checkmark + const checkMark = document.createElement("span"); + checkMark.classList.add('checkmark') + checkMark.textContent = menu.checked? '✓' : ' '; + item.appendChild(checkMark) + + //? display text + // @ts-ignore + // const [caption, keystroke] = menu.caption.split('\t') + const label = generateCaption(menu.caption); + label.classList.add('caption') + item.appendChild(label) + + //? keystroke + + const shortcut = document.createElement("span"); + shortcut.classList.add('keystroke') + shortcut.textContent = menu.type=='menuitem'? menu.shortcut : ''; + item.appendChild(shortcut) + + //? sub-menu sign + const chevron = document.createElement("span"); + chevron.classList.add('chevron') + chevron.textContent = menu.type=='popup'? '🞂' : ' '; + item.appendChild(chevron) + // item.textContent = `${menu.checked? '✓' : ' '} ${menu.caption}`; + + return item; +} + +function generateCaption(caption: string): HTMLElement { + const regex = /(&(\w))/gm; + const subst = `$2`; + + // The substituted value will be contained in the result variable + caption = caption.replace(regex, subst); + + const span = document.createElement("span"); + span.classList.add('caption') + span.innerHTML = caption; + return span +} \ No newline at end of file diff --git a/packages/webamp-modern/src/skin/makiClasses/Movable.ts b/packages/webamp-modern/src/skin/makiClasses/Movable.ts index 530d1215a5..ceb544d1b4 100644 --- a/packages/webamp-modern/src/skin/makiClasses/Movable.ts +++ b/packages/webamp-modern/src/skin/makiClasses/Movable.ts @@ -119,29 +119,31 @@ export default abstract class Movable extends GuiObj { CURSOR, CURSOR ); - const startX = downEvent.clientX; - const startY = downEvent.clientY; + const startX = downEvent.pageX; + const startY = downEvent.pageY; const handleMove = (moveEvent: MouseEvent) => { - const newMouseX = moveEvent.clientX; - const newMouseY = moveEvent.clientY; + const newMouseX = moveEvent.pageX; + const newMouseY = moveEvent.pageY; const deltaY = newMouseY - startY; const deltaX = newMouseX - startX; layout.setResizing("move", deltaX, deltaY); }; + const trottledMove = throttle(handleMove, 5); + const handleMouseUp = (upEvent: MouseEvent) => { upEvent.stopPropagation(); if (upEvent.button != 0) return; // only care LeftButton - document.removeEventListener("mousemove", handleMove); + document.removeEventListener("mousemove", trottledMove); document.removeEventListener("mouseup", handleMouseUp); - const newMouseX = upEvent.clientX; - const newMouseY = upEvent.clientY; + const newMouseX = upEvent.pageX; + const newMouseY = upEvent.pageY; const deltaY = newMouseY - startY; const deltaX = newMouseX - startX; layout.setResizing("final", deltaX, deltaY); }; - document.addEventListener("mousemove", throttle(handleMove, 75)); + document.addEventListener("mousemove", trottledMove); document.addEventListener("mouseup", handleMouseUp); }; @@ -165,29 +167,32 @@ export default abstract class Movable extends GuiObj { if (downEvent.button != 0) return; // only care LeftButton const layout = this.getparentlayout() as Layout; layout.setMoving("start", 0, 0); - const startX = downEvent.clientX; - const startY = downEvent.clientY; + const startX = downEvent.pageX; + const startY = downEvent.pageY; const handleMove = (moveEvent: MouseEvent) => { - const newMouseX = moveEvent.clientX; - const newMouseY = moveEvent.clientY; + const newMouseX = moveEvent.pageX; + const newMouseY = moveEvent.pageY; const deltaY = newMouseY - startY; const deltaX = newMouseX - startX; + // console.log("move", deltaX, deltaY); layout.setMoving("move", deltaX, deltaY); }; + const trottledMove = throttle(handleMove, 5); + const handleMouseUp = (upEvent: MouseEvent) => { if (upEvent.button != 0) return; // only care LeftButton upEvent.stopPropagation(); - document.removeEventListener("mousemove", handleMove); + document.removeEventListener("mousemove", trottledMove); document.removeEventListener("mouseup", handleMouseUp); - const newMouseX = upEvent.clientX; - const newMouseY = upEvent.clientY; + const newMouseX = upEvent.pageX; + const newMouseY = upEvent.pageY; const deltaY = newMouseY - startY; const deltaX = newMouseX - startX; layout.setMoving("final", deltaX, deltaY); }; - document.addEventListener("mousemove", throttle(handleMove, 10)); + document.addEventListener("mousemove", trottledMove); document.addEventListener("mouseup", handleMouseUp); }; diff --git a/packages/webamp-modern/src/skin/makiClasses/PopupMenu.ts b/packages/webamp-modern/src/skin/makiClasses/PopupMenu.ts index dd7e53239d..0f7b6bfc08 100644 --- a/packages/webamp-modern/src/skin/makiClasses/PopupMenu.ts +++ b/packages/webamp-modern/src/skin/makiClasses/PopupMenu.ts @@ -1,23 +1,26 @@ import BaseObject from "./BaseObject"; -import { assume } from "../../utils"; +import { assume, px } from "../../utils"; +import { MenuItem, IPopupMenu, generatePopupDiv, extractCaption, ICLoseablePopup, destroyActivePopup, setActivePopup, deactivePopup, IMenuItem } from "./MenuItem"; +import { Skin, UIRoot } from "../../UIRoot"; +import { registerAction } from "./menuWa5actions"; // import { sleep } from 'deasync'; // import { deasync } from '@kaciras/deasync'; // import sp from 'synchronized-promise'; // taken from sp test -const asyncFunctionBuilder = - (success) => - (value, timeouts = 1000) => { - return new Promise((resolve, reject) => { - setTimeout(function () { - if (success) { - resolve(value); - } else { - reject(new TypeError(value)); - } - }, timeouts); - }); - }; +// const asyncFunctionBuilder = +// (success) => +// (value, timeouts = 1000) => { +// return new Promise((resolve, reject) => { +// setTimeout(function () { +// if (success) { +// resolve(value); +// } else { +// reject(new TypeError(value)); +// } +// }, timeouts); +// }); +// }; // const async_sleep = (timeout) => { // // setTimeout(() => done(null, "wake up!"), timeout); // const done = () => {} @@ -26,24 +29,9 @@ const asyncFunctionBuilder = // const sleep = sp(async_sleep) // const sleep = sp(asyncFunctionBuilder(true)) -export type MenuItem = - | { - type: "menuitem"; - caption: string; - id: number; - checked: boolean; - disabled?: boolean; - } - | { type: "separator" } - | { - type: "popup"; - caption: string; - popup: PopupMenu; - disabled?: boolean; - children?: MenuItem[]; - }; - function waitPopup(popup: PopupMenu): Promise { + + function waitPopup(popup: PopupMenu, x=0, y=0): Promise { // const closePopup = () => div.remove(); // https://stackoverflow.com/questions/54916739/wait-for-click-event-inside-a-for-loop-similar-to-prompt @@ -55,119 +43,86 @@ export type MenuItem = acc(id); }; const div = generatePopupDiv(popup, itemClick); + if(x || y){ + div.style.left = px(x); + div.style.top = px(y); + } document.getElementById("web-amp").appendChild(div); - const closePopup = () => div.remove() + const closePopup = () => { + div.remove() + popup._successPromise = null + } + const outsideClick = (ret:number) => { + closePopup() + acc(ret); + } + popup._successPromise = outsideClick function handleClick() { document.removeEventListener('click', handleClick); closePopup() acc(-1); } - document.addEventListener('click', handleClick); + document.addEventListener("click", handleClick); }); // return 1; } -export function generatePopupDiv(popup: PopupMenu, callback: Function): HTMLElement { - const root = document.createElement("ul"); - root.className = 'popup-menu-container' - // root.style.zIndex = "1000"; - // console.log('generating popup:', popup) - for (const menu of popup._items) { - // const menuitem = document.createElement("li"); - let item: HTMLElement; - // root.appendChild(item); - switch (menu.type) { - case "menuitem": - item = generatePopupItem(menu); - item.onclick = (e) => callback(menu.id); - break; - case "popup": - item = generatePopupItem(menu); - const subMenu = generatePopupDiv(menu.popup, callback); - item.appendChild(subMenu) - break; - case "separator": - item = document.createElement("hr"); - break; - } - root.appendChild(item); - } - return root; -} - -//? one row of popup -function generatePopupItem(menu: MenuItem): HTMLElement { - const item = document.createElement("li"); - - //? checkmark - const checkMark = document.createElement("span"); - checkMark.classList.add('checkmark') - checkMark.textContent = menu.checked? '✓' : ' '; - item.appendChild(checkMark) +export default class PopupMenu extends BaseObject implements IPopupMenu, ICLoseablePopup { + static GUID = "f4787af44ef7b2bb4be7fb9c8da8bea9"; + children: MenuItem[] = []; + _uiRoot: UIRoot; - //? display text - const [caption, keystroke] = menu.caption.split('\t') - const label = generateCaption(caption); - label.classList.add('caption') - item.appendChild(label) - - //? keystroke - const shortcut = document.createElement("span"); - shortcut.classList.add('keystroke') - shortcut.textContent = keystroke; - item.appendChild(shortcut) - - //? sub-menu sign - const chevron = document.createElement("span"); - chevron.classList.add('chevron') - chevron.textContent = menu.type=='popup'? '⮀' : ' '; - item.appendChild(chevron) - // item.textContent = `${menu.checked? '✓' : ' '} ${menu.caption}`; - - return item; -} + constructor(uiRoot: UIRoot) { + super(); + this._uiRoot = uiRoot; -function generateCaption(caption: string): HTMLElement { - const regex = /(&(\w))/gm; - const subst = `$2`; - - // The substituted value will be contained in the result variable - caption = caption.replace(regex, subst); - - const span = document.createElement("span"); - span.classList.add('caption') - span.innerHTML = caption; - return span -} - -export default class PopupMenu extends BaseObject { - static GUID = "f4787af44ef7b2bb4be7fb9c8da8bea9"; - _items: MenuItem[] = []; - addcommand( + // this._div = document.createElement( + // this.getElTag().toLowerCase().replace("_", "") + // ); + } + private _addcommand( cmdText: string, cmd_id: number, - checked: boolean, - disabled: boolean + checked: boolean=false, + disabled: boolean=false, + data: {[key:string]: any}={} ) { - this._items.push({ + this.children.push({ type: "menuitem", - caption: cmdText, + // caption: cmdText, + ...extractCaption(cmdText), id: cmd_id, checked, disabled, + data }); + + } + addcommand( + cmdText: string, + cmd_id: number, + checked: boolean, + disabled: boolean + ) { + + if(cmd_id==32767) { + this._loadSkins() + return + } + this._addcommand(cmdText, cmd_id, checked, disabled) } addseparator() { - this._items.push({ type: "separator" }); + this.children.push({ type: "separator" }); } addsubmenu(popup: PopupMenu, submenutext: string) { - this._items.push({ type: "popup", popup: popup, caption: submenutext }); + // this.children.push({ type: "popup", popup: popup, caption: submenutext }); + this.children.push({ type: "popup", popup: popup, ...extractCaption(submenutext) }); // // TODO: // this.addcommand(submenutext, 0, false, false) } checkcommand(cmd_id: number, check: boolean) { - const item = this._items.find((item) => { + const item = this.children.find((item) => { return item.type === "menuitem" && item.id === cmd_id; }); assume(item != null, `Could not find item with id "${cmd_id}"`); @@ -177,25 +132,64 @@ export default class PopupMenu extends BaseObject { item.checked = check; } disablecommand(cmd_id: number, disable: boolean) { - for (const item of this._items) { + for (const item of this.children) { if (item.type == "menuitem" && item.id == cmd_id) { item.disabled = disable; break; } } } + async popatmouse(): Promise { console.log('popAtMouse.start...:') - const result = await waitPopup(this) + const mousePos = this._uiRoot._mousePos; + // const result = await waitPopup(this, mousePos.x, mousePos.y) + const result = await this.popatxy(mousePos.x, mousePos.y) console.log('popAtMouse.return:', result) return result; } async popatxy(x:number, y:number):Promise{ - return await waitPopup(this) + destroyActivePopup() + setActivePopup(this) + const ret = await waitPopup(this, x, y) + deactivePopup(this) + // setActivePopup(this) + // this._showButton(this._elDown); + // this._div.classList.add("open"); + + // ACTIVE_MENU = this; + return ret; + } + + /** + * called by such Menu to close this pupup in favour of + * that Menu want to show their own popup (user click that Menu) + */ + doClosePopup(){ + if(this._successPromise){ + this._successPromise(-1) + } + } + _successPromise: Function = null + + _loadSkins(){ + let action_id = 32767; + this._uiRoot._skins.forEach(skin => { + const name = typeof skin === 'string' ? skin : skin.name; + const url = typeof skin === 'string' ? skin : skin.url; + const skin_info: Skin = {name, url} + action_id++; + + registerAction(action_id, { //? Skin, checked or not + onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => {menu.checked = uiRoot.getSkinName() == menu.caption || uiRoot.getSkinUrl() == menu.data.url }, + onExecute: (uiRoot: UIRoot) => { uiRoot.switchSkin(skin_info); return true }, + }) + this._addcommand(name, action_id, false, false, skin_info) + }) } // popatmouse(): number { - // const message = this._items.map((item) => { + // const message = this.children.map((item) => { // switch (item.type) { // case "separator": // return "------"; @@ -206,7 +200,7 @@ export default class PopupMenu extends BaseObject { // message.unshift("Pick the number matching your choice:\n"); // let choice: number | null = null; // while ( - // !this._items.some((item) => item.type === "item" && item.id === choice) + // !this.children.some((item) => item.type === "item" && item.id === choice) // ) { // choice = Number(window.prompt(message.join("\n"))); // if (choice == 0) break; @@ -219,6 +213,16 @@ export default class PopupMenu extends BaseObject { // return this.popatmouse(); // } getnumcommands() { - return this._items.length; + return this.children.length; } + + hideMenu(cmd_id: number) { + for (const item of this.children) { + if (item.type == "menuitem" && item.id == cmd_id) { + item.invisible = true; + break; + } + } + } + } diff --git a/packages/webamp-modern/src/skin/makiClasses/Text.ts b/packages/webamp-modern/src/skin/makiClasses/Text.ts index f3f4946fb1..7fd6851556 100644 --- a/packages/webamp-modern/src/skin/makiClasses/Text.ts +++ b/packages/webamp-modern/src/skin/makiClasses/Text.ts @@ -66,6 +66,9 @@ export default class Text extends GuiObj { case "default": // (str) A static string to be displayed. // console.log('THETEXT', value) + // if (value.startsWith(':')) { + // value = this._interpolateText(value) + // } this._text = value; this._renderText(); break; @@ -348,6 +351,24 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x ]); } + _interpolateText(value: string): string { + switch (value.toLowerCase()) { + case ":componentname": + const layout = this.getparentlayout(); + if (layout) { + //debugger + try{ + // sometime error with wasabi + return layout.getcontainer()._name || value; + } catch { + return value + } + } + break; + } + return value + } + gettext(): string { if (this._alternateText) { // alternate text is used in Winamp3 to show a hint of a Play button while mouse down. @@ -542,7 +563,7 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x _getBitmapFontTextWidth(font: BitmapFont): number { const charWidth = font._charWidth; - return this.gettext().length * charWidth + this._paddingX * 2; + return this.gettext().length * charWidth; } _getTrueTypeTextWidth(font: TrueTypeFont): number { @@ -554,14 +575,22 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x * * @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393 */ - const self = this; + // const self = this; + let txt = this.gettext(); + if (this._forceuppercase) { + txt = txt.toUpperCase() + } else if (this._forcelowercase) { + txt = txt.toLowerCase() + } + const fontFamily = (font && font.getFontFamily()) || '"Liberation Sans", "DejaVu Sans", Arial'; + const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); - context.font = `${this._fontSize || 11}px ${ - (font && font.getFontFamily()) || "Arial" - }`; + context.font = `${this._bold ? '700' : ''} ${this._fontSize || 11}px ${fontFamily}`; + const metrics = context.measureText(this.gettext()); - return metrics.width + self._paddingX * 2; + + return Math.ceil(metrics.width /*+ self._paddingX * 2*/); } draw() { diff --git a/packages/webamp-modern/src/skin/makiClasses/menuWa5.ts b/packages/webamp-modern/src/skin/makiClasses/menuWa5.ts index c691681e0a..7114221fdf 100644 --- a/packages/webamp-modern/src/skin/makiClasses/menuWa5.ts +++ b/packages/webamp-modern/src/skin/makiClasses/menuWa5.ts @@ -1,19 +1,24 @@ -import PopupMenu, { MenuItem } from "./PopupMenu"; +import { UIRoot } from "../../UIRoot"; +import PopupMenu from "./PopupMenu"; +import { MenuItem } from "./MenuItem"; -export function getWa5Popup(popupId: string): PopupMenu { +export function getWa5Popup(popupId: string, uiRoot: UIRoot): PopupMenu { if(['PE_Help', 'ML_Help'].includes(popupId)) popupId = 'Help'; - const res = wa5commonRes.includes(popupId) ? wa5commonRes : wa5miscRes.includes(popupId) ? popupId : ''; - // const popupJson =getPopupJson(popupId, res); + else if(popupId.toLowerCase()=='presets') popupId = 'EQpresets' + const id = `POPUP "${popupId}"`; + const res = wa5commonRes.includes(id) ? wa5commonRes : wa5miscRes.includes(id) ? wa5miscRes : wa5controlRes; + // const res = wa5commonRes.includes(popupId) ? wa5commonRes : wa5miscRes.includes(popupId) ? popupId : ''; + const popup =getPopupJson(popupId, res, uiRoot); // console.log('FOUND', popupId, popupJson) - const popup =getPopupMenu(popupId, res); + // const popup =getPopupMenu(popupId, res); console.log('FOUND', popupId, popup) return popup; } -function getPopupJson(popupId: string, res:string): MenuItem[] { - const root = []; - let container = root; - let levelStack = [root]; +function getPopupJson(popupId: string, res:string, uiRoot: UIRoot): PopupMenu { + let root: PopupMenu; + // let container = root; + // let levelStack = [root]; let popup: PopupMenu = null; let popupStack: PopupMenu[] = []; let found = false; @@ -59,21 +64,23 @@ function getPopupJson(popupId: string, res:string): MenuItem[] { const menu: MenuItem = { type, }; - container.push(menu); // attach to prent + // container.push(menu); // attach to prent switch (menu.type) { case 'popup': - const newPopup = new PopupMenu(); + const newPopup = new PopupMenu(uiRoot); if(popup){ // if it is a sub-popup, attach to parent popup.addsubmenu(newPopup, t2) + } else { + root = newPopup } popup = newPopup; popupStack.push(popup); menu.popup = popup; menu.caption = t2; - menu.children = []; - container = menu.children; + // menu.children = []; + // container = menu.children; if (flags.indexOf('GRAYED') >= 0) menu.disabled = true; - levelStack.push(container) + // levelStack.push(container) break; case 'menuitem': menu.caption = t2; @@ -96,10 +103,10 @@ function getPopupJson(popupId: string, res:string): MenuItem[] { } else if (['}', 'END'].includes(line.trim())) { // Menutup menu saat ini - levelStack.pop(); - container = levelStack[levelStack.length - 1]; + // levelStack.pop(); + // container = levelStack[levelStack.length - 1]; if(found) { - if(container == root) + if(popup == root) break; popupStack.pop(); @@ -111,7 +118,7 @@ function getPopupJson(popupId: string, res:string): MenuItem[] { return root; } -function getPopupMenu(popupId: string, res:string): PopupMenu { +function getPopupMenu(popupId: string, res:string, uiRoot: UIRoot): PopupMenu { let root: PopupMenu; // let container = root; // let levelStack = [root]; @@ -163,7 +170,7 @@ function getPopupMenu(popupId: string, res:string): PopupMenu { // container.push(menu); // attach to prent switch (menu.type) { case 'popup': - const newPopup = new PopupMenu(); + const newPopup = new PopupMenu(uiRoot); if(popup){ // if it is a sub-popup, attach to parent popup.addsubmenu(newPopup, t2) } else { @@ -173,7 +180,7 @@ function getPopupMenu(popupId: string, res:string): PopupMenu { popupStack.push(popup); menu.popup = popup; menu.caption = t2; - menu.children = []; + // menu.children = []; // container = menu.children; if (flags.indexOf('GRAYED') >= 0) menu.disabled = true; // levelStack.push(container) @@ -182,8 +189,11 @@ function getPopupMenu(popupId: string, res:string): PopupMenu { menu.caption = t2; menu.id = id; // if(flags.indexOf('GRAYED') >= 0) menu.disabled = true; - menu.disabled = flags.indexOf('GRAYED') >= 0; + menu.disabled = flags.indexOf('GRAYED') != -1; popup.addcommand(t2, id, false, menu.disabled) + if(flags.indexOf('HIDDEN') != -1){ + popup.hideMenu(id) + } break; case 'separator': popup.addseparator() @@ -314,8 +324,9 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US } } MENUITEM "", 0, MFT_SEPARATOR, MFS_ENABLED - MENUITEM "Time &elapsed Ctrl+T toggles", 40037, MFT_STRING, MFS_ENABLED - MENUITEM "Time re&maining Ctrl+T toggles", 40038, MFT_STRING, MFS_ENABLED + MENUITEM "&Time elapsed Alt+T toggles", 40037, MFT_STRING, MFS_ENABLED + MENUITEM "Time re&maining Alt+T toggles", 40038, MFT_STRING, MFS_ENABLED + MENUITEM "Time remaining toggle Alt+T", 40039, MFT_STRING, WEBAMP_HIDDEN MENUITEM "", 0, MFT_SEPARATOR, MFS_ENABLED MENUITEM "&Always On Top Ctrl+A", 40019, MFT_STRING, MFS_ENABLED MENUITEM "&Double Size Ctrl+D", 40165, MFT_STRING, MFS_ENABLED @@ -721,4 +732,73 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US } } } -`; \ No newline at end of file +`; + +//from gen_ff.dll +const wa5controlRes = `1281 MENU +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +{ + POPUP "ControlMenu" + { + POPUP "&Opacity" + { + MENUITEM "1&00%", 42211 + MENUITEM "&90%", 42210 + MENUITEM "&80%", 42209 + MENUITEM "&70%", 42208 + MENUITEM "&60%", 42207 + MENUITEM "&50%", 42206 + MENUITEM "&40%", 42205 + MENUITEM "&30%", 42204 + MENUITEM "&20%", 42203 + MENUITEM "&10%", 42202 + MENUITEM "&Custom", 42227 + MENUITEM SEPARATOR + MENUITEM "Opaque on &Focus", 42228 + MENUITEM "Opaque on &Hover", 42226 + } + POPUP "&Scaling" + { + MENUITEM "&50%", 42214 + MENUITEM "&75%", 42215 + MENUITEM "&100%", 42216 + MENUITEM "150%", 42222 + MENUITEM "&200%", 42217 + MENUITEM "250%", 42218 + MENUITEM "&300%", 42219 + MENUITEM "&Custom", 42224 + MENUITEM SEPARATOR + MENUITEM "&Locked", 42223 + MENUITEM "&Temporary", 42225 + } + POPUP "Docked Toolbar", 65535, MFT_STRING, MFS_GRAYED, 0 + { + MENUITEM "Auto-&Hide", 42235 + MENUITEM "&Always On Top", 42234 + MENUITEM SEPARATOR + MENUITEM "Top", 42229 + MENUITEM "Left", 42236 + MENUITEM "Right", 42231 + MENUITEM "Bottom", 42232 + MENUITEM "Not docked", 42233 + MENUITEM SEPARATOR + MENUITEM "Dock/Undock Windows by Dragging", 42237 + } + } +}` + +const ffCustomScaleDialog = `1286 DIALOGEX 0, 0, 165, 70 +STYLE DS_SYSMODAL | DS_SHELLFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_TOOLWINDOW +CAPTION "Custom Scale" +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +FONT 8, "MS Shell Dlg" +{ + CONTROL "Scale : 100%", 1026, BUTTON, BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 7, 7, 151, 37 + CONTROL "Slider1", 1025, "msctls_trackbar32", TBS_HORZ | TBS_BOTH | TBS_NOTICKS | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 13, 17, 139, 11 + CONTROL "10%", -1, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 17, 30, 14, 8 + CONTROL "300%", -1, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 131, 30, 18, 8 + CONTROL "OK", 1, BUTTON, BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 54, 50, 50, 13 + CONTROL "Cancel", 2, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 108, 50, 50, 13 +} +` diff --git a/packages/webamp-modern/src/skin/makiClasses/menuWa5actions.ts b/packages/webamp-modern/src/skin/makiClasses/menuWa5actions.ts new file mode 100644 index 0000000000..4428dd57b0 --- /dev/null +++ b/packages/webamp-modern/src/skin/makiClasses/menuWa5actions.ts @@ -0,0 +1,92 @@ +import { Skin, UIRoot } from "../../UIRoot"; +import PopupMenu from "./PopupMenu"; +import { IMenuItem, IPopupMenu, MenuItem } from "./MenuItem"; + +type MenuActionEvent = (menu: MenuItem, uiRoot: UIRoot) => void; +type MenuActionExecution = (uiRoot: UIRoot) => boolean | void; + +type MenuAction = { + onUpdate?: MenuActionEvent; //? attemp to update disability, checkmark, visiblity, etc + onExecute?: MenuActionExecution; //? function to run when menu is clicked +} +const dummyAction: MenuAction = { + onUpdate: (menu: MenuItem) => { }, + onExecute: (uiRoot: UIRoot) => false, +} + +export const actions: { [key: number]: MenuAction } = {}; +export const findAction = (menuId:number): MenuAction => { + const registeredAction = actions[menuId] || {} + return {...dummyAction, ...registeredAction} +} +export async function updateActions(popup: IPopupMenu, uiRoot: UIRoot) { + return await Promise.all( + popup.children.map(async (menuItem) => { + if (menuItem.type == 'menuitem') { + const action = findAction(menuItem.id); + action.onUpdate(menuItem, uiRoot) + } else if (menuItem.type == 'popup') { + await updateActions(menuItem.popup, uiRoot) + } + }) + ); +} + +export const registerAction = (menuId: number, action: MenuAction) => { + actions[menuId] = action; +} + +registerAction(40037, { //? Time elapsed + onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => { menu.checked = !uiRoot.audio._timeRemaining }, + onExecute: (uiRoot: UIRoot) => { uiRoot.audio._timeRemaining = false; return true }, +}) + +registerAction(40038, { //? Time remaining + onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => { menu.checked = uiRoot.audio._timeRemaining }, + onExecute: (uiRoot: UIRoot) => { uiRoot.audio._timeRemaining = true; return true }, +}) + +registerAction(40039, { //? Time remaining + onExecute: (uiRoot: UIRoot) => { uiRoot.audio.toggleRemainingTime(); return true }, +}) + +registerAction(40044, { //? Previous + onExecute: (uiRoot: UIRoot) => uiRoot.dispatch('prev') +}) + +registerAction(40045, { //? Play + onExecute: (uiRoot: UIRoot) => uiRoot.dispatch('play') +}) + +registerAction(40046, { //? Pause + onExecute: (uiRoot: UIRoot) => uiRoot.dispatch('pause') +}) + +registerAction(40047, { //? Stop + onExecute: (uiRoot: UIRoot) => uiRoot.dispatch('stop') +}) + +registerAction(40048, { //? Next + onExecute: (uiRoot: UIRoot) => uiRoot.dispatch('next') +}) + + +registerAction(11111140038, { //? + onUpdate: (menu: MenuItem) => { } +}) + +registerAction(40244, { //? Equalizer Enabled + onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => { menu.checked = uiRoot.audio._eqEnabled }, + onExecute: (uiRoot: UIRoot) => { uiRoot.eq_toggle(); return true }, +}) + +registerAction(40040, { //? View/ Playlist Editor + onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => {menu.checked = uiRoot.getActionState('toggle', 'guid:pl')}, + onExecute: (uiRoot: UIRoot) => { uiRoot.dispatch('toggle', 'guid:pl'); return true }, +}) + +// registerAction(32767, { //? Skin, checked or not +// onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => {menu.checked = uiRoot.getSkinName() == menu.caption}, +// onExecute: (uiRoot: UIRoot) => { uiRoot.switchSkin(menu.data as Skin); return true }, +// }) + From cd2ac88c4babb267a79e15f2bb9d0fc1e6dc25da Mon Sep 17 00:00:00 2001 From: Fathony L Date: Wed, 22 May 2024 18:57:15 +0700 Subject: [PATCH 2/2] lines format --- packages/webamp-modern/src/UIRoot.ts | 84 ++-- packages/webamp-modern/src/index.ts | 46 +- .../src/skin/makiClasses/GuiObj.ts | 24 +- .../src/skin/makiClasses/Layout.ts | 43 +- .../src/skin/makiClasses/Menu.ts | 47 +- .../src/skin/makiClasses/MenuItem.ts | 118 ++--- .../src/skin/makiClasses/PopupMenu.ts | 140 +++--- .../src/skin/makiClasses/SystemObject.ts | 2 +- .../src/skin/makiClasses/Text.ts | 20 +- .../webamp-modern/src/skin/makiClasses/Vis.ts | 60 ++- .../src/skin/makiClasses/menuWa5.ts | 434 +++++++++--------- .../src/skin/makiClasses/menuWa5actions.ts | 176 ++++--- packages/webamp-modern/src/xp/menuparser3.ts | 48 +- 13 files changed, 687 insertions(+), 555 deletions(-) diff --git a/packages/webamp-modern/src/UIRoot.ts b/packages/webamp-modern/src/UIRoot.ts index 3c23c293d2..e637fe9d6b 100644 --- a/packages/webamp-modern/src/UIRoot.ts +++ b/packages/webamp-modern/src/UIRoot.ts @@ -31,7 +31,11 @@ import Avs from "./skin/makiClasses/Avs"; import { getWa5Popup } from "./skin/makiClasses/menuWa5"; import { SkinEngineClass } from "./skin/SkinEngine"; -import { FileExtractor, PathFileExtractor, ZipFileExtractor, } from "./skin/FileExtractor"; +import { + FileExtractor, + PathFileExtractor, + ZipFileExtractor, +} from "./skin/FileExtractor"; import Application from "./skin/makiClasses/Application"; import { @@ -40,8 +44,7 @@ import { SkinEngine, } from "./skin/SkinEngine"; - -export type Skin = | string | {name: string, url: string}; +export type Skin = string | { name: string; url: string }; export class UIRoot { _id: string; @@ -51,7 +54,7 @@ export class UIRoot { _winampConfig: WinampConfig; _div: HTMLDivElement = document.createElement("div"); - _mousePos : {x:number,y:number} = {x:0, y:0} + _mousePos: { x: number; y: number } = { x: 0, y: 0 }; _imageManager: ImageManager; // Just a temporary place to stash things _bitmaps: { [id: string]: Bitmap } = {}; @@ -74,8 +77,8 @@ export class UIRoot { _xFades: GroupXFade[] = []; _input: HTMLInputElement = document.createElement("input"); _skinInfo: { [key: string]: string } = {}; - _skin: Skin = {name:'', url:''}; - _skins: Skin[] = [] + _skin: Skin = { name: "", url: "" }; + _skins: Skin[] = []; _skinEngineClass: SkinEngineClass; _eventListener: Emitter = new Emitter(); _additionalCss: string[] = []; @@ -103,7 +106,7 @@ export class UIRoot { this._winampConfig = new WinampConfig(this); this.playlist = new PlEdit(this); // must be after _config. this.vm = new Vm(this); - this.setlistenMouseMove(true) + this.setlistenMouseMove(true); } getId(): string { @@ -531,7 +534,11 @@ export class UIRoot { cssEl.textContent = cssRules.join("\n"); } - dispatch(action: string, param?: string | null, actionTarget?: string | null) { + dispatch( + action: string, + param?: string | null, + actionTarget?: string | null + ) { switch (action.toLowerCase()) { case "play": this.audio.play(); @@ -573,36 +580,40 @@ export class UIRoot { break; case "menu": - getWa5Popup(param, this).popatmouse() + getWa5Popup(param, this).popatmouse(); break; case "controlmenu": - getWa5Popup('ControlMenu', this).popatmouse() + getWa5Popup("ControlMenu", this).popatmouse(); break; case "sysmenu": - getWa5Popup('Main', this).popatmouse() + getWa5Popup("Main", this).popatmouse(); break; case "pe_add": - getWa5Popup('Add', this).popatmouse() + getWa5Popup("Add", this).popatmouse(); break; case "pe_rem": - getWa5Popup('Remove', this).popatmouse() + getWa5Popup("Remove", this).popatmouse(); break; case "pe_sel": - getWa5Popup('Select', this).popatmouse() + getWa5Popup("Select", this).popatmouse(); break; case "pe_misc": - getWa5Popup('MiscOpt', this).popatmouse() + getWa5Popup("MiscOpt", this).popatmouse(); break; case "pe_list": - getWa5Popup('Playlist', this).popatmouse() + getWa5Popup("Playlist", this).popatmouse(); break; default: assume(false, `Unknown global action: ${action}`); } } - getActionState(action: string, param: string, actionTarget: string=''): boolean { + getActionState( + action: string, + param: string, + actionTarget: string = "" + ): boolean { if (action != null) { switch (action.toLowerCase()) { case "eq_toggle": @@ -694,15 +705,18 @@ export class UIRoot { } } - setlistenMouseMove( listen: boolean ){ - const update = (e:MouseEvent) => { + setlistenMouseMove(listen: boolean) { + const update = (e: MouseEvent) => { this._mousePos = { // https://stackoverflow.com/questions/6073505/what-is-the-difference-between-screenx-y-clientx-y-and-pagex-y - x : e.pageX, - y : e.pageY - } - } - window.document[`${listen?'add':'remove'}EventListener`]('mousemove', update); + x: e.pageX, + y: e.pageY, + }; + }; + window.document[`${listen ? "add" : "remove"}EventListener`]( + "mousemove", + update + ); } //? Zip things ======================== @@ -771,14 +785,14 @@ export class UIRoot { } setSkinInfo(skinInfo: { [key: string]: string }) { - const url = this._skinInfo.url - this._skinInfo = {...skinInfo, url}; + const url = this._skinInfo.url; + this._skinInfo = { ...skinInfo, url }; } - setSkinUrl(url:string){ + setSkinUrl(url: string) { this._skinInfo.url = url; } - getSkinUrl(){ - return this._skinInfo.url + getSkinUrl() { + return this._skinInfo.url; } getSkinInfo(): { [key: string]: string } { return this._skinInfo; @@ -800,10 +814,9 @@ export class UIRoot { // this._parent.appendChild(this._uiRoot.getRootDiv()); parentDiv.appendChild(this.getRootDiv()); - const name = typeof skin === 'string' ? skin : skin.name; - const skinPath = typeof skin === 'string' ? skin : skin.url; - this.setSkinUrl(skinPath) - + const name = typeof skin === "string" ? skin : skin.name; + const skinPath = typeof skin === "string" ? skin : skin.url; + this.setSkinUrl(skinPath); let skinFetched = false; let SkinEngineClass = null; @@ -832,8 +845,7 @@ export class UIRoot { //? success found a skin-engine this.SkinEngineClass = SkinEngineClass; const parser: SkinEngine = new SkinEngineClass(this); - if (!skinFetched) - await this._loadSkinPathToUiroot(skinPath, parser); + if (!skinFetched) await this._loadSkinPathToUiroot(skinPath, parser); // await parser.parseSkin(); await parser.buildUI(); @@ -886,8 +898,6 @@ export class UIRoot { this.setFileExtractor(fileExtractor); } - - set SkinEngineClass(Engine: SkinEngineClass) { this._skinEngineClass = Engine; } diff --git a/packages/webamp-modern/src/index.ts b/packages/webamp-modern/src/index.ts index f91812bf15..30e5cf503f 100644 --- a/packages/webamp-modern/src/index.ts +++ b/packages/webamp-modern/src/index.ts @@ -109,14 +109,22 @@ async function initializeSkinListMenu() { // MODERN { filename: "[Winamp] default", download_url: "" }, { filename: "[Winamp] MMD3", download_url: "assets/skins/MMD3.wal" }, - { filename: "[Winamp] MMD3+Thinger", download_url: "assets/skins/MMD3+Thinger/" }, + { + filename: "[Winamp] MMD3+Thinger", + download_url: "assets/skins/MMD3+Thinger/", + }, // { filename: "[Folder] MMD3", download_url: "assets/skins/extracted/MMD3/" }, { filename: "[Winamp] BigBento", download_url: "assets/skins/BigBento/" }, - { filename: "CornerAmp_Redux", download_url: "assets/skins/CornerAmp_Redux.wal" }, + { + filename: "CornerAmp_Redux", + download_url: "assets/skins/CornerAmp_Redux.wal", + }, - // CLASSIC - { filename: "[Winamp Classic]", download_url: "assets/skins/base-2.91.wsz" }, + { + filename: "[Winamp Classic]", + download_url: "assets/skins/base-2.91.wsz", + }, { filename: "[Winamp Classic] MacOSXAqua1-5", download_url: "assets/skins/MacOSXAqua1-5.698dd4ab.wsz", @@ -131,7 +139,10 @@ async function initializeSkinListMenu() { filename: "[wmp] Quicksilver WindowsMediaPlayer!", download_url: "assets/skins/Quicksilver.wmz", }, - { filename: "[wmp] Windows XP", download_url: "assets/skins/Windows-XP.wmz" }, + { + filename: "[wmp] Windows XP", + download_url: "assets/skins/Windows-XP.wmz", + }, { filename: "[wmp] Famous Headspace", download_url: "assets/skins/Headspace.wmz", @@ -156,7 +167,10 @@ async function initializeSkinListMenu() { }, // K-JOFOL - { filename: "[K-Jofol] Default", download_url: "assets/skins/Default.kjofol" }, + { + filename: "[K-Jofol] Default", + download_url: "assets/skins/Default.kjofol", + }, { filename: "[K-Jofol] Illusion 1.0", download_url: "assets/skins/Illusion1-0.kjofol", @@ -165,7 +179,10 @@ async function initializeSkinListMenu() { filename: "[K-Jofol] K-Nine 05r", download_url: "assets/skins/K-Nine05r.kjofol", }, - { filename: "[K-Jofol] Limus 2.0", download_url: "assets/skins/Limus2-0.zip" }, + { + filename: "[K-Jofol] Limus 2.0", + download_url: "assets/skins/Limus2-0.zip", + }, // SONIQUE { filename: "[Sonique] Default", download_url: "assets/skins/sonique.sgf" }, @@ -177,17 +194,26 @@ async function initializeSkinListMenu() { filename: "[Sonique] Panthom (SkinBuilder)", download_url: "assets/skins/phantom.sgf", }, - { filename: "[Sonique] ChainZ and", download_url: "assets/skins/ChainZ-and.sgf" }, + { + filename: "[Sonique] ChainZ and", + download_url: "assets/skins/ChainZ-and.sgf", + }, // COWON JET-AUDIO { filename: "[JetAudio] Small Bar", download_url: "assets/skins/DefaultBar_s.jsk", }, - { filename: "[Cowon JetAudio] Gold", download_url: "assets/skins/Gold.uib" }, + { + filename: "[Cowon JetAudio] Gold", + download_url: "assets/skins/Gold.uib", + }, // AIMP - { filename: "[AIMP] Flo-4K", download_url: "assets/skins/AIMP-Flo-4K.acs5" }, + { + filename: "[AIMP] Flo-4K", + download_url: "assets/skins/AIMP-Flo-4K.acs5", + }, ]; const skins = [...internalSkins, ...bankskin1]; diff --git a/packages/webamp-modern/src/skin/makiClasses/GuiObj.ts b/packages/webamp-modern/src/skin/makiClasses/GuiObj.ts index 6b177181cd..5ed16cf882 100644 --- a/packages/webamp-modern/src/skin/makiClasses/GuiObj.ts +++ b/packages/webamp-modern/src/skin/makiClasses/GuiObj.ts @@ -20,29 +20,29 @@ import { XmlElement } from "@rgrove/parse-xml"; let BRING_LEAST: number = -1; let BRING_MOST_TOP: number = 1; -const globalMouseDown:Function[] = []; -export const installGlobalMouseDown = (f:Function) => { - if(!globalMouseDown.includes(f)){ - globalMouseDown.push(f) +const globalMouseDown: Function[] = []; +export const installGlobalMouseDown = (f: Function) => { + if (!globalMouseDown.includes(f)) { + globalMouseDown.push(f); } -} -export const uninstallGlobalMouseDown = (f:Function) => { +}; +export const uninstallGlobalMouseDown = (f: Function) => { const index = globalMouseDown.indexOf(f); - if(index!=-1){ + if (index != -1) { // delete globalMouseDown[index] - globalMouseDown.splice(index, 1) + globalMouseDown.splice(index, 1); } -} +}; -export const executeGlobalMouseDown = (e:MouseEvent) => { +export const executeGlobalMouseDown = (e: MouseEvent) => { for (let i = 0; i < globalMouseDown.length; i++) { // console.log(typeof globalMouseDown[i], '>>', globalMouseDown[i]); globalMouseDown[i](e); } // for (let mousedown of globalMouseDown) { // mousedown(e) - // } -} + // } +}; // http://wiki.winamp.com/wiki/XML_GUI_Objects#GuiObject_.28Global_params.29 export default class GuiObj extends XmlObj { diff --git a/packages/webamp-modern/src/skin/makiClasses/Layout.ts b/packages/webamp-modern/src/skin/makiClasses/Layout.ts index 348d643592..efde7d2284 100644 --- a/packages/webamp-modern/src/skin/makiClasses/Layout.ts +++ b/packages/webamp-modern/src/skin/makiClasses/Layout.ts @@ -20,7 +20,7 @@ export default class Layout extends Group { static GUID = "60906d4e482e537e94cc04b072568861"; _resizingDiv: HTMLDivElement = null; _resizing: boolean = false; - _resizing_start : DOMRect = null; + _resizing_start: DOMRect = null; _canResize: number = 0; // combination of 4 directions: N/E/W/S _scale: number = 1.0; _opacity: number = 1.0; @@ -29,7 +29,7 @@ export default class Layout extends Group { _movingStartY: number; _moving: boolean = false; _snap = { left: 0, top: 0, right: 0, bottom: 0 }; - _shortcuts: {[key:string]:number} = {}; + _shortcuts: { [key: string]: number } = {}; constructor(uiRoot: UIRoot) { super(uiRoot); @@ -201,7 +201,7 @@ export default class Layout extends Group { // this._resizingDiv.style.height = px(r.height); // this._resizingDiv.style.top = px(container.gettop()); // this._resizingDiv.style.left = px(container.getleft()); - const {left,top,width,height} = r + const { left, top, width, height } = r; this._resizingDiv.style.cssText = ` width: ${px(width)}; height: ${px(height)}; @@ -217,28 +217,26 @@ export default class Layout extends Group { return; } // console.log(`resizing dx:${dx} dy:${dy}`); - let {left,top,width,height, right, bottom} = this._resizing_start - if (this._canResize & RIGHT) - width = clampW(width + dx); - if (this._canResize & BOTTOM) - height = (clampH(height + dy)); + let { left, top, width, height, right, bottom } = this._resizing_start; + if (this._canResize & RIGHT) width = clampW(width + dx); + if (this._canResize & BOTTOM) height = clampH(height + dy); if (this._canResize & LEFT) { - width = (clampW(width + -dx)); - let l = (left + dx); - if(l+width <= right) { + width = clampW(width + -dx); + let l = left + dx; + if (l + width <= right) { left = l; } else { - left = right - this._minimumWidth + left = right - this._minimumWidth; } } if (this._canResize & TOP) { - height = (clampH(height + -dy)); - let t = (top + dy); - if(t+height <= bottom) { + height = clampH(height + -dy); + let t = top + dy; + if (t + height <= bottom) { top = t; } else { - top = bottom - this._minimumHeight + top = bottom - this._minimumHeight; } } this._resizingDiv.style.cssText = ` @@ -247,7 +245,6 @@ export default class Layout extends Group { left: ${px(left)}; top: ${px(top)}; `; - } else if (cmd == "final") { if (!this._resizing) { return; @@ -296,17 +293,17 @@ export default class Layout extends Group { } // MENU SHORTCUT HANDLER HERE ====================== - registerShortcuts(popup: PopupMenu){ + registerShortcuts(popup: PopupMenu) { forEachMenuItem(popup, (m: IMenuItem) => { - if(m.shortcut){ - this._shortcuts[m.shortcut] = m.id + if (m.shortcut) { + this._shortcuts[m.shortcut] = m.id; } - }) + }); // console.log('layout.shortcuts:', this._shortcuts) } - executeShorcut(shortcut:string){ - const menuId = this._shortcuts[shortcut] + executeShorcut(shortcut: string) { + const menuId = this._shortcuts[shortcut]; const action = findAction(menuId); // console.log('Layout:', this._name, 'executing shortcut:',shortcut, '@action.id:', menuId, '=:', action ) const invalidateRequired = action.onExecute(this._uiRoot); diff --git a/packages/webamp-modern/src/skin/makiClasses/Menu.ts b/packages/webamp-modern/src/skin/makiClasses/Menu.ts index e67f07d0e3..68a6d1c83b 100644 --- a/packages/webamp-modern/src/skin/makiClasses/Menu.ts +++ b/packages/webamp-modern/src/skin/makiClasses/Menu.ts @@ -6,10 +6,15 @@ import Group from "./Group"; import Layer from "./Layer"; import { getWa5Popup } from "./menuWa5"; import PopupMenu from "./PopupMenu"; -import { ICLoseablePopup, destroyActivePopup, generatePopupDiv, setActivePopup } from "./MenuItem"; +import { + ICLoseablePopup, + destroyActivePopup, + generatePopupDiv, + setActivePopup, +} from "./MenuItem"; import { findAction, updateActions } from "./menuWa5actions"; -let ACTIVE_MENU_GROUP: string = '' +let ACTIVE_MENU_GROUP: string = ""; // let ACTIVE_MENU: Menu = null; /*function destroyActivePopup() { @@ -148,21 +153,20 @@ export default class Menu extends Group implements ICLoseablePopup { //? toggle dropdown visibility if (ACTIVE_MENU_GROUP != this._menuGroupId) { ACTIVE_MENU_GROUP = this._menuGroupId; - destroyActivePopup() + destroyActivePopup(); // setTimeout(() => { // installGlobalMouseDown(globalWindowClick); // }, 500); // installGlobalClickListener() - setActivePopup(this) - + setActivePopup(this); } else { ACTIVE_MENU_GROUP = null; // if (ACTIVE_MENU != null) { // ACTIVE_MENU.doCloseMenu() // } - destroyActivePopup() + destroyActivePopup(); } - this.onEnterArea() + this.onEnterArea(); } onEnterArea() { // super.onEnterArea(); @@ -170,12 +174,12 @@ export default class Menu extends Group implements ICLoseablePopup { // if (ACTIVE_MENU != null) { // ACTIVE_MENU.doCloseMenu() // } - destroyActivePopup() + destroyActivePopup(); this._showButton(this._elDown); this._div.classList.add("open"); // ACTIVE_MENU = this; - setActivePopup(this) + setActivePopup(this); } else { this._showButton(this._elHover); } @@ -188,12 +192,11 @@ export default class Menu extends Group implements ICLoseablePopup { } } - setup() { super.setup(); // this.resolveButtonsAction(); // this._uiRoot.vm.dispatch(this, "onstartup", []); - this.getparentlayout().registerShortcuts(this._popup) + this.getparentlayout().registerShortcuts(this._popup); } resolveButtonsAction() { //console.log('found img') @@ -233,12 +236,11 @@ export default class Menu extends Group implements ICLoseablePopup { // this._div.classList.remove("vertical"); // } - - if (this._menuId.startsWith('WA5:')) { - const [, popupId] = this._menuId.split(':') + if (this._menuId.startsWith("WA5:")) { + const [, popupId] = this._menuId.split(":"); this._popup = getWa5Popup(popupId, this._uiRoot); - - this.invalidatePopup() + + this.invalidatePopup(); // function menuClick(id:number){ // console.log('menu clicked:', id) // } @@ -251,21 +253,20 @@ export default class Menu extends Group implements ICLoseablePopup { invalidatePopup() { const self = this; if (this._popup) { - // destroy old DOM - if(this._popupDiv){ - this._popupDiv.remove() + if (this._popupDiv) { + this._popupDiv.remove(); } updateActions(this._popup, this._uiRoot); // let winamp5 menus reflect the real config/condition const menuItemClick = (id: number) => { - console.log('menu clicked:', id); + console.log("menu clicked:", id); const action = findAction(id); const invalidateRequired = action.onExecute(self._uiRoot); - if(invalidateRequired) self.invalidatePopup(); - } - + if (invalidateRequired) self.invalidatePopup(); + }; + this._popupDiv = generatePopupDiv(this._popup, menuItemClick); // } else { // this._popupDiv = document.createElement("div"); diff --git a/packages/webamp-modern/src/skin/makiClasses/MenuItem.ts b/packages/webamp-modern/src/skin/makiClasses/MenuItem.ts index 47afce46f2..cd7bcf2e83 100644 --- a/packages/webamp-modern/src/skin/makiClasses/MenuItem.ts +++ b/packages/webamp-modern/src/skin/makiClasses/MenuItem.ts @@ -2,7 +2,7 @@ import { installGlobalMouseDown, uninstallGlobalMouseDown } from "./GuiObj"; export interface IPopupMenu { children: MenuItem[]; -} +} export type IMenuItem = { type: "menuitem"; @@ -10,15 +10,15 @@ export type IMenuItem = { id: number; checked: boolean; disabled?: boolean; - shortcut?: string; // "Ctrl+Alt+Shift+A" - keychar?: string; // 'p' of "&Play" - invisible?: boolean;// special case to register shortcut only - data?:{[key:string]: any}; // used by skin's popup item -} + shortcut?: string; // "Ctrl+Alt+Shift+A" + keychar?: string; // 'p' of "&Play" + invisible?: boolean; // special case to register shortcut only + data?: { [key: string]: any }; // used by skin's popup item +}; type IMenuSeparator = { - type: "separator" -} + type: "separator"; +}; type IMenuPopup = { type: "popup"; @@ -29,7 +29,7 @@ type IMenuPopup = { children?: MenuItem[]; }; -export type MenuItem = | IMenuItem | IMenuSeparator | IMenuPopup; +export type MenuItem = IMenuItem | IMenuSeparator | IMenuPopup; export interface ICLoseablePopup { doClosePopup: Function; @@ -40,38 +40,39 @@ export interface ICLoseablePopup { let ACTIVE_POPUP: ICLoseablePopup = null; export function destroyActivePopup() { - console.log('globalWindowClick') + console.log("globalWindowClick"); if (ACTIVE_POPUP != null) { - ACTIVE_POPUP.doClosePopup() + ACTIVE_POPUP.doClosePopup(); } // ACTIVE_MENU_GROUP = '' - uninstallGlobalClickListener() + uninstallGlobalClickListener(); } export function setActivePopup(popup: ICLoseablePopup) { ACTIVE_POPUP = popup; - if(popup){ - installGlobalClickListener() + if (popup) { + installGlobalClickListener(); } else { - uninstallGlobalClickListener() + uninstallGlobalClickListener(); } } /** * if the active == popup => set null - * @param popup + * @param popup */ export function deactivePopup(popup: ICLoseablePopup) { - if(popup == ACTIVE_POPUP){ - setActivePopup(null) + if (popup == ACTIVE_POPUP) { + setActivePopup(null); } } let globalClickInstalled = false; function installGlobalClickListener() { - setTimeout(() => { // using promise to prevent immediately executing of globalWindowClick + setTimeout(() => { + // using promise to prevent immediately executing of globalWindowClick if (!globalClickInstalled) { - installGlobalMouseDown(destroyActivePopup); // call globalWindowClick on any GuiObj + installGlobalMouseDown(destroyActivePopup); // call globalWindowClick on any GuiObj document.addEventListener("mousedown", destroyActivePopup); // call globalWindowClick on document globalClickInstalled = true; } @@ -82,40 +83,43 @@ function uninstallGlobalClickListener() { if (globalClickInstalled) { document.removeEventListener("mousedown", destroyActivePopup); globalClickInstalled = false; - uninstallGlobalMouseDown(destroyActivePopup) + uninstallGlobalMouseDown(destroyActivePopup); } - } // ################## MenuItem Utils ##########################33 -export function forEachMenuItem(popup: IPopupMenu, callback:Function){ - for(const menu of popup.children){ - if(menu.type=="menuitem"){ - callback(menu) - } - else if(menu.type=="popup"){ - forEachMenuItem(menu.popup, callback) +export function forEachMenuItem(popup: IPopupMenu, callback: Function) { + for (const menu of popup.children) { + if (menu.type == "menuitem") { + callback(menu); + } else if (menu.type == "popup") { + forEachMenuItem(menu.popup, callback); } } } type ExtractedCaptions = { caption: string; - shortcut?: string; // "Ctrl+Alt+Shift+A" - keychar?: string; // 'p' of "&Play" -} + shortcut?: string; // "Ctrl+Alt+Shift+A" + keychar?: string; // 'p' of "&Play" +}; -export function extractCaption(text:string): ExtractedCaptions{ +export function extractCaption(text: string): ExtractedCaptions { // const result:ExtractedCaptions = {caption:text, shortcut:null, keychar:''} - const [caption, shortcut] = text.split('\t') - const keychar = caption.includes('&')? caption[caption.indexOf('&')+1].toLowerCase() : '' - return {caption, shortcut, keychar}; + const [caption, shortcut] = text.split("\t"); + const keychar = caption.includes("&") + ? caption[caption.indexOf("&") + 1].toLowerCase() + : ""; + return { caption, shortcut, keychar }; } -export function generatePopupDiv(popup: IPopupMenu, callback: Function): HTMLElement { +export function generatePopupDiv( + popup: IPopupMenu, + callback: Function +): HTMLElement { const root = document.createElement("ul"); - root.className = 'popup-menu-container' + root.className = "popup-menu-container"; // root.style.zIndex = "1000"; // console.log('generating popup:', popup) for (const menu of popup.children) { @@ -124,7 +128,7 @@ export function generatePopupDiv(popup: IPopupMenu, callback: Function): HTMLEle // root.appendChild(item); switch (menu.type) { case "menuitem": - if(menu.invisible===true){ + if (menu.invisible === true) { continue; } item = generatePopupItem(menu); @@ -134,7 +138,7 @@ export function generatePopupDiv(popup: IPopupMenu, callback: Function): HTMLEle case "popup": item = generatePopupItem(menu); const subMenu = generatePopupDiv(menu.popup, callback); - item.appendChild(subMenu) + item.appendChild(subMenu); break; case "separator": item = document.createElement("hr"); @@ -146,34 +150,34 @@ export function generatePopupDiv(popup: IPopupMenu, callback: Function): HTMLEle } //? one row of popup -function generatePopupItem(menu: | IMenuItem | IMenuPopup): HTMLElement { +function generatePopupItem(menu: IMenuItem | IMenuPopup): HTMLElement { const item = document.createElement("li"); //? checkmark const checkMark = document.createElement("span"); - checkMark.classList.add('checkmark') - checkMark.textContent = menu.checked? '✓' : ' '; - item.appendChild(checkMark) - + checkMark.classList.add("checkmark"); + checkMark.textContent = menu.checked ? "✓" : " "; + item.appendChild(checkMark); + //? display text // @ts-ignore - // const [caption, keystroke] = menu.caption.split('\t') + // const [caption, keystroke] = menu.caption.split('\t') const label = generateCaption(menu.caption); - label.classList.add('caption') - item.appendChild(label) + label.classList.add("caption"); + item.appendChild(label); //? keystroke const shortcut = document.createElement("span"); - shortcut.classList.add('keystroke') - shortcut.textContent = menu.type=='menuitem'? menu.shortcut : ''; - item.appendChild(shortcut) + shortcut.classList.add("keystroke"); + shortcut.textContent = menu.type == "menuitem" ? menu.shortcut : ""; + item.appendChild(shortcut); //? sub-menu sign const chevron = document.createElement("span"); - chevron.classList.add('chevron') - chevron.textContent = menu.type=='popup'? '🞂' : ' '; - item.appendChild(chevron) + chevron.classList.add("chevron"); + chevron.textContent = menu.type == "popup" ? "🞂" : " "; + item.appendChild(chevron); // item.textContent = `${menu.checked? '✓' : ' '} ${menu.caption}`; return item; @@ -187,7 +191,7 @@ function generateCaption(caption: string): HTMLElement { caption = caption.replace(regex, subst); const span = document.createElement("span"); - span.classList.add('caption') + span.classList.add("caption"); span.innerHTML = caption; - return span -} \ No newline at end of file + return span; +} diff --git a/packages/webamp-modern/src/skin/makiClasses/PopupMenu.ts b/packages/webamp-modern/src/skin/makiClasses/PopupMenu.ts index 0f7b6bfc08..e8e0582d18 100644 --- a/packages/webamp-modern/src/skin/makiClasses/PopupMenu.ts +++ b/packages/webamp-modern/src/skin/makiClasses/PopupMenu.ts @@ -1,6 +1,16 @@ import BaseObject from "./BaseObject"; import { assume, px } from "../../utils"; -import { MenuItem, IPopupMenu, generatePopupDiv, extractCaption, ICLoseablePopup, destroyActivePopup, setActivePopup, deactivePopup, IMenuItem } from "./MenuItem"; +import { + MenuItem, + IPopupMenu, + generatePopupDiv, + extractCaption, + ICLoseablePopup, + destroyActivePopup, + setActivePopup, + deactivePopup, + IMenuItem, +} from "./MenuItem"; import { Skin, UIRoot } from "../../UIRoot"; import { registerAction } from "./menuWa5actions"; // import { sleep } from 'deasync'; @@ -29,38 +39,36 @@ import { registerAction } from "./menuWa5actions"; // const sleep = sp(async_sleep) // const sleep = sp(asyncFunctionBuilder(true)) +function waitPopup(popup: PopupMenu, x = 0, y = 0): Promise { + // const closePopup = () => div.remove(); - - function waitPopup(popup: PopupMenu, x=0, y=0): Promise { - // const closePopup = () => div.remove(); - - // https://stackoverflow.com/questions/54916739/wait-for-click-event-inside-a-for-loop-similar-to-prompt - return new Promise(acc => { + // https://stackoverflow.com/questions/54916739/wait-for-click-event-inside-a-for-loop-similar-to-prompt + return new Promise((acc) => { // let result: number = -1; const itemClick = (id: number) => { - closePopup() + closePopup(); // result = id; acc(id); }; const div = generatePopupDiv(popup, itemClick); - if(x || y){ + if (x || y) { div.style.left = px(x); div.style.top = px(y); } document.getElementById("web-amp").appendChild(div); const closePopup = () => { - div.remove() - popup._successPromise = null - } - const outsideClick = (ret:number) => { - closePopup() + div.remove(); + popup._successPromise = null; + }; + const outsideClick = (ret: number) => { + closePopup(); acc(ret); - } - popup._successPromise = outsideClick + }; + popup._successPromise = outsideClick; function handleClick() { - document.removeEventListener('click', handleClick); - closePopup() + document.removeEventListener("click", handleClick); + closePopup(); acc(-1); } document.addEventListener("click", handleClick); @@ -68,11 +76,14 @@ import { registerAction } from "./menuWa5actions"; // return 1; } -export default class PopupMenu extends BaseObject implements IPopupMenu, ICLoseablePopup { +export default class PopupMenu + extends BaseObject + implements IPopupMenu, ICLoseablePopup +{ static GUID = "f4787af44ef7b2bb4be7fb9c8da8bea9"; children: MenuItem[] = []; _uiRoot: UIRoot; - + constructor(uiRoot: UIRoot) { super(); this._uiRoot = uiRoot; @@ -84,9 +95,9 @@ export default class PopupMenu extends BaseObject implements IPopupMenu, ICLosea private _addcommand( cmdText: string, cmd_id: number, - checked: boolean=false, - disabled: boolean=false, - data: {[key:string]: any}={} + checked: boolean = false, + disabled: boolean = false, + data: { [key: string]: any } = {} ) { this.children.push({ type: "menuitem", @@ -95,29 +106,31 @@ export default class PopupMenu extends BaseObject implements IPopupMenu, ICLosea id: cmd_id, checked, disabled, - data + data, }); - - } + } addcommand( cmdText: string, cmd_id: number, checked: boolean, disabled: boolean ) { - - if(cmd_id==32767) { - this._loadSkins() - return + if (cmd_id == 32767) { + this._loadSkins(); + return; } - this._addcommand(cmdText, cmd_id, checked, disabled) + this._addcommand(cmdText, cmd_id, checked, disabled); } addseparator() { this.children.push({ type: "separator" }); } addsubmenu(popup: PopupMenu, submenutext: string) { // this.children.push({ type: "popup", popup: popup, caption: submenutext }); - this.children.push({ type: "popup", popup: popup, ...extractCaption(submenutext) }); + this.children.push({ + type: "popup", + popup: popup, + ...extractCaption(submenutext), + }); // // TODO: // this.addcommand(submenutext, 0, false, false) } @@ -141,51 +154,59 @@ export default class PopupMenu extends BaseObject implements IPopupMenu, ICLosea } async popatmouse(): Promise { - console.log('popAtMouse.start...:') + console.log("popAtMouse.start...:"); const mousePos = this._uiRoot._mousePos; // const result = await waitPopup(this, mousePos.x, mousePos.y) - const result = await this.popatxy(mousePos.x, mousePos.y) - console.log('popAtMouse.return:', result) + const result = await this.popatxy(mousePos.x, mousePos.y); + console.log("popAtMouse.return:", result); return result; } - async popatxy(x:number, y:number):Promise{ - destroyActivePopup() - setActivePopup(this) - const ret = await waitPopup(this, x, y) - deactivePopup(this) + async popatxy(x: number, y: number): Promise { + destroyActivePopup(); + setActivePopup(this); + const ret = await waitPopup(this, x, y); + deactivePopup(this); // setActivePopup(this) - // this._showButton(this._elDown); - // this._div.classList.add("open"); + // this._showButton(this._elDown); + // this._div.classList.add("open"); - // ACTIVE_MENU = this; + // ACTIVE_MENU = this; return ret; } /** - * called by such Menu to close this pupup in favour of + * called by such Menu to close this pupup in favour of * that Menu want to show their own popup (user click that Menu) */ - doClosePopup(){ - if(this._successPromise){ - this._successPromise(-1) + doClosePopup() { + if (this._successPromise) { + this._successPromise(-1); } } - _successPromise: Function = null + _successPromise: Function = null; - _loadSkins(){ + _loadSkins() { let action_id = 32767; - this._uiRoot._skins.forEach(skin => { - const name = typeof skin === 'string' ? skin : skin.name; - const url = typeof skin === 'string' ? skin : skin.url; - const skin_info: Skin = {name, url} + this._uiRoot._skins.forEach((skin) => { + const name = typeof skin === "string" ? skin : skin.name; + const url = typeof skin === "string" ? skin : skin.url; + const skin_info: Skin = { name, url }; action_id++; - registerAction(action_id, { //? Skin, checked or not - onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => {menu.checked = uiRoot.getSkinName() == menu.caption || uiRoot.getSkinUrl() == menu.data.url }, - onExecute: (uiRoot: UIRoot) => { uiRoot.switchSkin(skin_info); return true }, - }) - this._addcommand(name, action_id, false, false, skin_info) - }) + registerAction(action_id, { + //? Skin, checked or not + onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => { + menu.checked = + uiRoot.getSkinName() == menu.caption || + uiRoot.getSkinUrl() == menu.data.url; + }, + onExecute: (uiRoot: UIRoot) => { + uiRoot.switchSkin(skin_info); + return true; + }, + }); + this._addcommand(name, action_id, false, false, skin_info); + }); } // popatmouse(): number { @@ -224,5 +245,4 @@ export default class PopupMenu extends BaseObject implements IPopupMenu, ICLosea } } } - } diff --git a/packages/webamp-modern/src/skin/makiClasses/SystemObject.ts b/packages/webamp-modern/src/skin/makiClasses/SystemObject.ts index 10681fdbc4..5f04bb6c20 100644 --- a/packages/webamp-modern/src/skin/makiClasses/SystemObject.ts +++ b/packages/webamp-modern/src/skin/makiClasses/SystemObject.ts @@ -622,7 +622,7 @@ export default class SystemObject extends BaseObject { */ newdynamiccontainer(container_id: string): Container { //TODO - return unimplemented(null) + return unimplemented(null); } /** diff --git a/packages/webamp-modern/src/skin/makiClasses/Text.ts b/packages/webamp-modern/src/skin/makiClasses/Text.ts index 7fd6851556..24901b9189 100644 --- a/packages/webamp-modern/src/skin/makiClasses/Text.ts +++ b/packages/webamp-modern/src/skin/makiClasses/Text.ts @@ -357,16 +357,16 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x const layout = this.getparentlayout(); if (layout) { //debugger - try{ + try { // sometime error with wasabi return layout.getcontainer()._name || value; } catch { - return value + return value; } } break; } - return value + return value; } gettext(): string { @@ -578,18 +578,22 @@ offsety - (int) Extra pixels to be added to or subtracted from the calculated x // const self = this; let txt = this.gettext(); if (this._forceuppercase) { - txt = txt.toUpperCase() + txt = txt.toUpperCase(); } else if (this._forcelowercase) { - txt = txt.toLowerCase() + txt = txt.toLowerCase(); } - const fontFamily = (font && font.getFontFamily()) || '"Liberation Sans", "DejaVu Sans", Arial'; + const fontFamily = + (font && font.getFontFamily()) || + '"Liberation Sans", "DejaVu Sans", Arial'; const canvas = document.createElement("canvas"); const context = canvas.getContext("2d"); - context.font = `${this._bold ? '700' : ''} ${this._fontSize || 11}px ${fontFamily}`; + context.font = `${this._bold ? "700" : ""} ${ + this._fontSize || 11 + }px ${fontFamily}`; const metrics = context.measureText(this.gettext()); - + return Math.ceil(metrics.width /*+ self._paddingX * 2*/); } diff --git a/packages/webamp-modern/src/skin/makiClasses/Vis.ts b/packages/webamp-modern/src/skin/makiClasses/Vis.ts index 4f2ac1aad2..4a40571b26 100644 --- a/packages/webamp-modern/src/skin/makiClasses/Vis.ts +++ b/packages/webamp-modern/src/skin/makiClasses/Vis.ts @@ -21,23 +21,23 @@ export class VisPaintHandler { * Attemp to build cached bitmaps for later use while render a frame. * Purpose: fast rendering in animation loop */ - prepare() { } + prepare() {} /** * Called once per frame rendiring */ - paintFrame() { } + paintFrame() {} /** * Attemp to cleanup cached bitmaps */ - dispose() { } + dispose() {} /** * called if it is an AVS. * @param action vis_prev | vis_next | vis_f5 (fullscreen) | */ - doAction(action: string, param: string) { } + doAction(action: string, param: string) {} } // type VisPaintHandlerClass = {new(vis: Vis): VisPaintHandler;}; @@ -721,15 +721,19 @@ class WavePaintHandler extends VisPaintHandler { } if (using16temporaryCanvas) { - const canvas = this._vis._canvas - const visCtx = canvas.getContext('2d'); + const canvas = this._vis._canvas; + const visCtx = canvas.getContext("2d"); visCtx.clearRect(0, 0, canvas.width, canvas.height); visCtx.drawImage( this._16h, - 0, 0, // sx,sy - 72, 16, // sw,sh - 0, 0, //dx,dy - canvas.width, canvas.height //dw,dh + 0, + 0, // sx,sy + 72, + 16, // sw,sh + 0, + 0, //dx,dy + canvas.width, + canvas.height //dw,dh ); } } @@ -804,10 +808,14 @@ class WavePaintHandler extends VisPaintHandler { for (y = top; y <= bottom; y++) { this._ctx.drawImage( this._bar, - 0, colorIndex, // sx,sy - 1, 1, // sw,sh - x, y, //dx,dy - 1, 1 //dw,dh + 0, + colorIndex, // sx,sy + 1, + 1, // sw,sh + x, + y, //dx,dy + 1, + 1 //dw,dh ); } } @@ -815,10 +823,14 @@ class WavePaintHandler extends VisPaintHandler { paintWavDot(x: number, y: number, colorIndex: number) { this._ctx.drawImage( this._bar, - 0, colorIndex, // sx,sy - 1, 1, // sw,sh - x, y, //dx,dy - 1, 1 //dw,dh + 0, + colorIndex, // sx,sy + 1, + 1, // sw,sh + x, + y, //dx,dy + 1, + 1 //dw,dh ); } @@ -835,10 +847,14 @@ class WavePaintHandler extends VisPaintHandler { for (y = top; y <= bottom; y++) { this._ctx.drawImage( this._bar, - 0, colorIndex, // sx,sy - 1, 1, // sw,sh - x, y, //dx,dy - 1, 1 //dw,dh + 0, + colorIndex, // sx,sy + 1, + 1, // sw,sh + x, + y, //dx,dy + 1, + 1 //dw,dh ); } } diff --git a/packages/webamp-modern/src/skin/makiClasses/menuWa5.ts b/packages/webamp-modern/src/skin/makiClasses/menuWa5.ts index 7114221fdf..b3f206e6c8 100644 --- a/packages/webamp-modern/src/skin/makiClasses/menuWa5.ts +++ b/packages/webamp-modern/src/skin/makiClasses/menuWa5.ts @@ -3,225 +3,241 @@ import PopupMenu from "./PopupMenu"; import { MenuItem } from "./MenuItem"; export function getWa5Popup(popupId: string, uiRoot: UIRoot): PopupMenu { - if(['PE_Help', 'ML_Help'].includes(popupId)) popupId = 'Help'; - else if(popupId.toLowerCase()=='presets') popupId = 'EQpresets' - const id = `POPUP "${popupId}"`; - const res = wa5commonRes.includes(id) ? wa5commonRes : wa5miscRes.includes(id) ? wa5miscRes : wa5controlRes; - // const res = wa5commonRes.includes(popupId) ? wa5commonRes : wa5miscRes.includes(popupId) ? popupId : ''; - const popup =getPopupJson(popupId, res, uiRoot); - // console.log('FOUND', popupId, popupJson) - // const popup =getPopupMenu(popupId, res); - console.log('FOUND', popupId, popup) - return popup; + if (["PE_Help", "ML_Help"].includes(popupId)) popupId = "Help"; + else if (popupId.toLowerCase() == "presets") popupId = "EQpresets"; + const id = `POPUP "${popupId}"`; + const res = wa5commonRes.includes(id) + ? wa5commonRes + : wa5miscRes.includes(id) + ? wa5miscRes + : wa5controlRes; + // const res = wa5commonRes.includes(popupId) ? wa5commonRes : wa5miscRes.includes(popupId) ? popupId : ''; + const popup = getPopupJson(popupId, res, uiRoot); + // console.log('FOUND', popupId, popupJson) + // const popup =getPopupMenu(popupId, res); + console.log("FOUND", popupId, popup); + return popup; } -function getPopupJson(popupId: string, res:string, uiRoot: UIRoot): PopupMenu { - let root: PopupMenu; - // let container = root; - // let levelStack = [root]; - let popup: PopupMenu = null; - let popupStack: PopupMenu[] = []; - let found = false; - // let currentItem = null - - // Looping setiap baris pada string menu - for (let line of res.split('\n')) { - line = line.trim() - - if(!found && !line.startsWith('POPUP')) // skip until found popup - continue; - - // Mengabaikan baris yang tidak penting - if (!line || line.startsWith('//')) { - continue; +function getPopupJson(popupId: string, res: string, uiRoot: UIRoot): PopupMenu { + let root: PopupMenu; + // let container = root; + // let levelStack = [root]; + let popup: PopupMenu = null; + let popupStack: PopupMenu[] = []; + let found = false; + // let currentItem = null + + // Looping setiap baris pada string menu + for (let line of res.split("\n")) { + line = line.trim(); + + if (!found && !line.startsWith("POPUP")) + // skip until found popup + continue; + + // Mengabaikan baris yang tidak penting + if (!line || line.startsWith("//")) { + continue; + } + + // Mengambil level pada baris saat ini + // const level = line.search(/\S/); + + // Mengecek apakah baris merupakan menu + // const menuMatch = line.match(/\s*(POPUP|MENUITEM)\s+"([^"]+)"(?:\s*,\s*(\d+))?,\s*(\d+),\s*(\d+)/i); + const menuMatch = line.match( + /\s*(POPUP|MENUITEM)\s+(SEPARATOR|"([^"]*)")(?:\s*,\s*(\w+)[\s,]*(.*))?/i + ); + if (menuMatch) { + // console.log('match', menuMatch) + // Mengambil informasi menu + let [, tag, t1, t2, sid, flags] = menuMatch; + const type = + tag == "POPUP" + ? "popup" + : t1 == "SEPARATOR" || (flags || "").indexOf("MFT_SEPARATOR") >= 0 + ? "separator" + : "menuitem"; + const id = parseInt(sid); + + if (!found) { + if (type == "popup" && t2 == popupId) { + found = true; + } else { + continue; // skip } + } - // Mengambil level pada baris saat ini - // const level = line.search(/\S/); - - // Mengecek apakah baris merupakan menu - // const menuMatch = line.match(/\s*(POPUP|MENUITEM)\s+"([^"]+)"(?:\s*,\s*(\d+))?,\s*(\d+),\s*(\d+)/i); - const menuMatch = line.match(/\s*(POPUP|MENUITEM)\s+(SEPARATOR|"([^"]*)")(?:\s*,\s*(\w+)[\s,]*(.*))?/i); - if (menuMatch) { - // console.log('match', menuMatch) - // Mengambil informasi menu - let [, tag, t1, t2, sid, flags] = menuMatch; - const type = tag == 'POPUP' ? 'popup' : (t1 == 'SEPARATOR' || (flags || '').indexOf('MFT_SEPARATOR') >= 0) ? 'separator' : 'menuitem'; - const id = parseInt(sid) - - - if(!found) { - if(type=='popup' && t2 == popupId) { - found = true; - } else { - continue; // skip - } - } - - flags = flags || '' - // Membuat objek menu baru - // @ts-ignore - const menu: MenuItem = { - type, - }; - // container.push(menu); // attach to prent - switch (menu.type) { - case 'popup': - const newPopup = new PopupMenu(uiRoot); - if(popup){ // if it is a sub-popup, attach to parent - popup.addsubmenu(newPopup, t2) - } else { - root = newPopup - } - popup = newPopup; - popupStack.push(popup); - menu.popup = popup; - menu.caption = t2; - // menu.children = []; - // container = menu.children; - if (flags.indexOf('GRAYED') >= 0) menu.disabled = true; - // levelStack.push(container) - break; - case 'menuitem': - menu.caption = t2; - menu.id = id; - // if(flags.indexOf('GRAYED') >= 0) menu.disabled = true; - menu.disabled = flags.indexOf('GRAYED') >= 0; - popup.addcommand(t2, id, false, menu.disabled) - break; - case 'separator': - popup.addseparator() - break; - } - // const id = type=='popup'? 65535: type == 'separator' ? 0 : parseInt(sid); - - // console.log('m', newMenu, '>>', flags) - // @ts-ignore - // menu.flags = flags; - - // console.log('m', menu) - - } else if (['}', 'END'].includes(line.trim())) { - // Menutup menu saat ini - // levelStack.pop(); - // container = levelStack[levelStack.length - 1]; - if(found) { - if(popup == root) - break; - - popupStack.pop(); - popup = popupStack[popupStack.length - 1]; - } - } + flags = flags || ""; + // Membuat objek menu baru + // @ts-ignore + const menu: MenuItem = { + type, + }; + // container.push(menu); // attach to prent + switch (menu.type) { + case "popup": + const newPopup = new PopupMenu(uiRoot); + if (popup) { + // if it is a sub-popup, attach to parent + popup.addsubmenu(newPopup, t2); + } else { + root = newPopup; + } + popup = newPopup; + popupStack.push(popup); + menu.popup = popup; + menu.caption = t2; + // menu.children = []; + // container = menu.children; + if (flags.indexOf("GRAYED") >= 0) menu.disabled = true; + // levelStack.push(container) + break; + case "menuitem": + menu.caption = t2; + menu.id = id; + // if(flags.indexOf('GRAYED') >= 0) menu.disabled = true; + menu.disabled = flags.indexOf("GRAYED") >= 0; + popup.addcommand(t2, id, false, menu.disabled); + break; + case "separator": + popup.addseparator(); + break; + } + // const id = type=='popup'? 65535: type == 'separator' ? 0 : parseInt(sid); + + // console.log('m', newMenu, '>>', flags) + // @ts-ignore + // menu.flags = flags; + + // console.log('m', menu) + } else if (["}", "END"].includes(line.trim())) { + // Menutup menu saat ini + // levelStack.pop(); + // container = levelStack[levelStack.length - 1]; + if (found) { + if (popup == root) break; + + popupStack.pop(); + popup = popupStack[popupStack.length - 1]; + } } + } - return root; + return root; } -function getPopupMenu(popupId: string, res:string, uiRoot: UIRoot): PopupMenu { - let root: PopupMenu; - // let container = root; - // let levelStack = [root]; - let popup: PopupMenu = null; - let popupStack: PopupMenu[] = []; - let found = false; - // let currentItem = null - - // Looping setiap baris pada string menu - for (let line of res.split('\n')) { - line = line.trim() - - if(!found && !line.startsWith('POPUP')) // skip until found popup - continue; - - // Mengabaikan baris yang tidak penting - if (!line || line.startsWith('//')) { - continue; +function getPopupMenu(popupId: string, res: string, uiRoot: UIRoot): PopupMenu { + let root: PopupMenu; + // let container = root; + // let levelStack = [root]; + let popup: PopupMenu = null; + let popupStack: PopupMenu[] = []; + let found = false; + // let currentItem = null + + // Looping setiap baris pada string menu + for (let line of res.split("\n")) { + line = line.trim(); + + if (!found && !line.startsWith("POPUP")) + // skip until found popup + continue; + + // Mengabaikan baris yang tidak penting + if (!line || line.startsWith("//")) { + continue; + } + + // Mengambil level pada baris saat ini + // const level = line.search(/\S/); + + // Mengecek apakah baris merupakan menu + // const menuMatch = line.match(/\s*(POPUP|MENUITEM)\s+"([^"]+)"(?:\s*,\s*(\d+))?,\s*(\d+),\s*(\d+)/i); + const menuMatch = line.match( + /\s*(POPUP|MENUITEM)\s+(SEPARATOR|"([^"]*)")(?:\s*,\s*(\w+)[\s,]*(.*))?/i + ); + if (menuMatch) { + // console.log('match', menuMatch) + // Mengambil informasi menu + let [, tag, t1, t2, sid, flags] = menuMatch; + const type = + tag == "POPUP" + ? "popup" + : t1 == "SEPARATOR" || (flags || "").indexOf("MFT_SEPARATOR") >= 0 + ? "separator" + : "menuitem"; + const id = parseInt(sid); + + if (!found) { + if (type == "popup" && t2 == popupId) { + found = true; + } else { + continue; // skip } + } - // Mengambil level pada baris saat ini - // const level = line.search(/\S/); - - // Mengecek apakah baris merupakan menu - // const menuMatch = line.match(/\s*(POPUP|MENUITEM)\s+"([^"]+)"(?:\s*,\s*(\d+))?,\s*(\d+),\s*(\d+)/i); - const menuMatch = line.match(/\s*(POPUP|MENUITEM)\s+(SEPARATOR|"([^"]*)")(?:\s*,\s*(\w+)[\s,]*(.*))?/i); - if (menuMatch) { - // console.log('match', menuMatch) - // Mengambil informasi menu - let [, tag, t1, t2, sid, flags] = menuMatch; - const type = tag == 'POPUP' ? 'popup' : (t1 == 'SEPARATOR' || (flags || '').indexOf('MFT_SEPARATOR') >= 0) ? 'separator' : 'menuitem'; - const id = parseInt(sid) - - - if(!found) { - if(type=='popup' && t2 == popupId) { - found = true; - } else { - continue; // skip - } - } - - flags = flags || '' - // Membuat objek menu baru - // @ts-ignore - const menu: MenuItem = { - type, - }; - // container.push(menu); // attach to prent - switch (menu.type) { - case 'popup': - const newPopup = new PopupMenu(uiRoot); - if(popup){ // if it is a sub-popup, attach to parent - popup.addsubmenu(newPopup, t2) - } else { - root = newPopup - } - popup = newPopup; - popupStack.push(popup); - menu.popup = popup; - menu.caption = t2; - // menu.children = []; - // container = menu.children; - if (flags.indexOf('GRAYED') >= 0) menu.disabled = true; - // levelStack.push(container) - break; - case 'menuitem': - menu.caption = t2; - menu.id = id; - // if(flags.indexOf('GRAYED') >= 0) menu.disabled = true; - menu.disabled = flags.indexOf('GRAYED') != -1; - popup.addcommand(t2, id, false, menu.disabled) - if(flags.indexOf('HIDDEN') != -1){ - popup.hideMenu(id) - } - break; - case 'separator': - popup.addseparator() - break; - } - // const id = type=='popup'? 65535: type == 'separator' ? 0 : parseInt(sid); - - // console.log('m', newMenu, '>>', flags) - // @ts-ignore - // menu.flags = flags; - - // console.log('m', menu) - - } else if (['}', 'END'].includes(line.trim())) { - // Menutup menu saat ini - // levelStack.pop(); - // container = levelStack[levelStack.length - 1]; - if(found) { - if(popup == root) - break; - - popupStack.pop(); - popup = popupStack[popupStack.length - 1]; - } - } + flags = flags || ""; + // Membuat objek menu baru + // @ts-ignore + const menu: MenuItem = { + type, + }; + // container.push(menu); // attach to prent + switch (menu.type) { + case "popup": + const newPopup = new PopupMenu(uiRoot); + if (popup) { + // if it is a sub-popup, attach to parent + popup.addsubmenu(newPopup, t2); + } else { + root = newPopup; + } + popup = newPopup; + popupStack.push(popup); + menu.popup = popup; + menu.caption = t2; + // menu.children = []; + // container = menu.children; + if (flags.indexOf("GRAYED") >= 0) menu.disabled = true; + // levelStack.push(container) + break; + case "menuitem": + menu.caption = t2; + menu.id = id; + // if(flags.indexOf('GRAYED') >= 0) menu.disabled = true; + menu.disabled = flags.indexOf("GRAYED") != -1; + popup.addcommand(t2, id, false, menu.disabled); + if (flags.indexOf("HIDDEN") != -1) { + popup.hideMenu(id); + } + break; + case "separator": + popup.addseparator(); + break; + } + // const id = type=='popup'? 65535: type == 'separator' ? 0 : parseInt(sid); + + // console.log('m', newMenu, '>>', flags) + // @ts-ignore + // menu.flags = flags; + + // console.log('m', menu) + } else if (["}", "END"].includes(line.trim())) { + // Menutup menu saat ini + // levelStack.pop(); + // container = levelStack[levelStack.length - 1]; + if (found) { + if (popup == root) break; + + popupStack.pop(); + popup = popupStack[popupStack.length - 1]; + } } + } - return root; + return root; } const wa5commonRes = `256 MENUEX @@ -785,7 +801,7 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US MENUITEM "Dock/Undock Windows by Dragging", 42237 } } -}` +}`; const ffCustomScaleDialog = `1286 DIALOGEX 0, 0, 165, 70 STYLE DS_SYSMODAL | DS_SHELLFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU @@ -801,4 +817,4 @@ FONT 8, "MS Shell Dlg" CONTROL "OK", 1, BUTTON, BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 54, 50, 50, 13 CONTROL "Cancel", 2, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 108, 50, 50, 13 } -` +`; diff --git a/packages/webamp-modern/src/skin/makiClasses/menuWa5actions.ts b/packages/webamp-modern/src/skin/makiClasses/menuWa5actions.ts index 4428dd57b0..44a49c4fac 100644 --- a/packages/webamp-modern/src/skin/makiClasses/menuWa5actions.ts +++ b/packages/webamp-modern/src/skin/makiClasses/menuWa5actions.ts @@ -6,87 +6,119 @@ type MenuActionEvent = (menu: MenuItem, uiRoot: UIRoot) => void; type MenuActionExecution = (uiRoot: UIRoot) => boolean | void; type MenuAction = { - onUpdate?: MenuActionEvent; //? attemp to update disability, checkmark, visiblity, etc - onExecute?: MenuActionExecution; //? function to run when menu is clicked -} + onUpdate?: MenuActionEvent; //? attemp to update disability, checkmark, visiblity, etc + onExecute?: MenuActionExecution; //? function to run when menu is clicked +}; const dummyAction: MenuAction = { - onUpdate: (menu: MenuItem) => { }, - onExecute: (uiRoot: UIRoot) => false, -} + onUpdate: (menu: MenuItem) => {}, + onExecute: (uiRoot: UIRoot) => false, +}; export const actions: { [key: number]: MenuAction } = {}; -export const findAction = (menuId:number): MenuAction => { - const registeredAction = actions[menuId] || {} - return {...dummyAction, ...registeredAction} -} +export const findAction = (menuId: number): MenuAction => { + const registeredAction = actions[menuId] || {}; + return { ...dummyAction, ...registeredAction }; +}; export async function updateActions(popup: IPopupMenu, uiRoot: UIRoot) { - return await Promise.all( - popup.children.map(async (menuItem) => { - if (menuItem.type == 'menuitem') { - const action = findAction(menuItem.id); - action.onUpdate(menuItem, uiRoot) - } else if (menuItem.type == 'popup') { - await updateActions(menuItem.popup, uiRoot) - } - }) - ); + return await Promise.all( + popup.children.map(async (menuItem) => { + if (menuItem.type == "menuitem") { + const action = findAction(menuItem.id); + action.onUpdate(menuItem, uiRoot); + } else if (menuItem.type == "popup") { + await updateActions(menuItem.popup, uiRoot); + } + }) + ); } export const registerAction = (menuId: number, action: MenuAction) => { - actions[menuId] = action; -} - -registerAction(40037, { //? Time elapsed - onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => { menu.checked = !uiRoot.audio._timeRemaining }, - onExecute: (uiRoot: UIRoot) => { uiRoot.audio._timeRemaining = false; return true }, -}) - -registerAction(40038, { //? Time remaining - onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => { menu.checked = uiRoot.audio._timeRemaining }, - onExecute: (uiRoot: UIRoot) => { uiRoot.audio._timeRemaining = true; return true }, -}) - -registerAction(40039, { //? Time remaining - onExecute: (uiRoot: UIRoot) => { uiRoot.audio.toggleRemainingTime(); return true }, -}) - -registerAction(40044, { //? Previous - onExecute: (uiRoot: UIRoot) => uiRoot.dispatch('prev') -}) - -registerAction(40045, { //? Play - onExecute: (uiRoot: UIRoot) => uiRoot.dispatch('play') -}) - -registerAction(40046, { //? Pause - onExecute: (uiRoot: UIRoot) => uiRoot.dispatch('pause') -}) - -registerAction(40047, { //? Stop - onExecute: (uiRoot: UIRoot) => uiRoot.dispatch('stop') -}) - -registerAction(40048, { //? Next - onExecute: (uiRoot: UIRoot) => uiRoot.dispatch('next') -}) - - -registerAction(11111140038, { //? - onUpdate: (menu: MenuItem) => { } -}) - -registerAction(40244, { //? Equalizer Enabled - onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => { menu.checked = uiRoot.audio._eqEnabled }, - onExecute: (uiRoot: UIRoot) => { uiRoot.eq_toggle(); return true }, -}) - -registerAction(40040, { //? View/ Playlist Editor - onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => {menu.checked = uiRoot.getActionState('toggle', 'guid:pl')}, - onExecute: (uiRoot: UIRoot) => { uiRoot.dispatch('toggle', 'guid:pl'); return true }, -}) + actions[menuId] = action; +}; + +registerAction(40037, { + //? Time elapsed + onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => { + menu.checked = !uiRoot.audio._timeRemaining; + }, + onExecute: (uiRoot: UIRoot) => { + uiRoot.audio._timeRemaining = false; + return true; + }, +}); + +registerAction(40038, { + //? Time remaining + onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => { + menu.checked = uiRoot.audio._timeRemaining; + }, + onExecute: (uiRoot: UIRoot) => { + uiRoot.audio._timeRemaining = true; + return true; + }, +}); + +registerAction(40039, { + //? Time remaining + onExecute: (uiRoot: UIRoot) => { + uiRoot.audio.toggleRemainingTime(); + return true; + }, +}); + +registerAction(40044, { + //? Previous + onExecute: (uiRoot: UIRoot) => uiRoot.dispatch("prev"), +}); + +registerAction(40045, { + //? Play + onExecute: (uiRoot: UIRoot) => uiRoot.dispatch("play"), +}); + +registerAction(40046, { + //? Pause + onExecute: (uiRoot: UIRoot) => uiRoot.dispatch("pause"), +}); + +registerAction(40047, { + //? Stop + onExecute: (uiRoot: UIRoot) => uiRoot.dispatch("stop"), +}); + +registerAction(40048, { + //? Next + onExecute: (uiRoot: UIRoot) => uiRoot.dispatch("next"), +}); + +registerAction(11111140038, { + //? + onUpdate: (menu: MenuItem) => {}, +}); + +registerAction(40244, { + //? Equalizer Enabled + onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => { + menu.checked = uiRoot.audio._eqEnabled; + }, + onExecute: (uiRoot: UIRoot) => { + uiRoot.eq_toggle(); + return true; + }, +}); + +registerAction(40040, { + //? View/ Playlist Editor + onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => { + menu.checked = uiRoot.getActionState("toggle", "guid:pl"); + }, + onExecute: (uiRoot: UIRoot) => { + uiRoot.dispatch("toggle", "guid:pl"); + return true; + }, +}); // registerAction(32767, { //? Skin, checked or not // onUpdate: (menu: IMenuItem, uiRoot: UIRoot) => {menu.checked = uiRoot.getSkinName() == menu.caption}, // onExecute: (uiRoot: UIRoot) => { uiRoot.switchSkin(menu.data as Skin); return true }, // }) - diff --git a/packages/webamp-modern/src/xp/menuparser3.ts b/packages/webamp-modern/src/xp/menuparser3.ts index ef79c8c8b1..d68f537697 100644 --- a/packages/webamp-modern/src/xp/menuparser3.ts +++ b/packages/webamp-modern/src/xp/menuparser3.ts @@ -53,11 +53,10 @@ BEGIN BEGIN MENUITEM "&About ...", IDM_ABOUT,MFT_STRING,MFS_ENABLED END -END` +END`; - -// Untuk mengkonversi menu tersebut ke dalam format JSON, -// Anda dapat melakukan parsing manual dengan melakukan split string dan looping. +// Untuk mengkonversi menu tersebut ke dalam format JSON, +// Anda dapat melakukan parsing manual dengan melakukan split string dan looping. // Berikut ini adalah contoh implementasi menggunakan JavaScript: // Membuat fungsi untuk parsing string menu ke dalam format JSON @@ -68,9 +67,9 @@ function parseMenuToJson(menuContent) { // let currentItem = null // Looping setiap baris pada string menu - for (let line of menuContent.split('\n')) { + for (let line of menuContent.split("\n")) { // Mengabaikan baris yang tidak penting - if (!line || line.trim().startsWith('//')) { + if (!line || line.trim().startsWith("//")) { continue; } @@ -79,14 +78,21 @@ function parseMenuToJson(menuContent) { // Mengecek apakah baris merupakan menu // const menuMatch = line.match(/\s*(POPUP|MENUITEM)\s+"([^"]+)"(?:\s*,\s*(\d+))?,\s*(\d+),\s*(\d+)/i); - const menuMatch = line.match(/\s*(POPUP|MENUITEM)\s+(SEPARATOR|"([^"]*)")(?:\s*,\s*(\w+)[\s,]*(.*))?/i); + const menuMatch = line.match( + /\s*(POPUP|MENUITEM)\s+(SEPARATOR|"([^"]*)")(?:\s*,\s*(\w+)[\s,]*(.*))?/i + ); if (menuMatch) { // console.log('match', menuMatch) // Mengambil informasi menu let [, tag, t1, t2, id, flags] = menuMatch; - const type = tag == 'POPUP' ? 'popup' : (t1 == 'SEPARATOR' || (flags || '').indexOf('MFT_SEPARATOR') >= 0) ? 'separator' : 'menuitem'; - - flags = flags || '' + const type = + tag == "POPUP" + ? "popup" + : t1 == "SEPARATOR" || (flags || "").indexOf("MFT_SEPARATOR") >= 0 + ? "separator" + : "menuitem"; + + flags = flags || ""; // Membuat objek menu baru // @ts-ignore const menu: MenuItem = { @@ -104,18 +110,18 @@ function parseMenuToJson(menuContent) { }; container.push(menu); // attach to prent switch (menu.type) { - case 'popup': + case "popup": menu.caption = t2; menu.children = []; container = menu.children; - if (flags.indexOf('GRAYED') >= 0) menu.disabled = true; - levelStack.push(container) + if (flags.indexOf("GRAYED") >= 0) menu.disabled = true; + levelStack.push(container); break; - case 'menuitem': + case "menuitem": menu.caption = t2; menu.id = id; // if(flags.indexOf('GRAYED') >= 0) menu.disabled = true; - flags.indexOf('GRAYED') >= 0 && (menu.disabled = true) + flags.indexOf("GRAYED") >= 0 && (menu.disabled = true); break; default: @@ -127,7 +133,7 @@ function parseMenuToJson(menuContent) { // @ts-ignore // menu.flags = flags; - console.log('m', menu) + console.log("m", menu); // Menambahkan objek menu baru ke dalam parent menu yang sesuai // if (level > levelStack[levelStack.length - 1]) { @@ -142,7 +148,7 @@ function parseMenuToJson(menuContent) { // currentItem.children.push(newMenu); // currentItem = newMenu; // } - } else if (['}', 'END'].includes(line.trim())) { + } else if (["}", "END"].includes(line.trim())) { // Menutup menu saat ini levelStack.pop(); container = levelStack[levelStack.length - 1]; @@ -152,8 +158,8 @@ function parseMenuToJson(menuContent) { return root; } -var m = parseMenuToJson(menuContent) -console.log(m) +var m = parseMenuToJson(menuContent); +console.log(m); -var m = parseMenuToJson(another_sample) -console.log(m) \ No newline at end of file +var m = parseMenuToJson(another_sample); +console.log(m);