[Skin:Q] How to position & color elements...with variables?

To discuss development of addons / skins / customization of MediaMonkey v5 / v2024

Moderators: jiri, drakinite, Addon Administrators

peter_h
Posts: 146
Joined: Mon Mar 12, 2007 4:38 pm

[Skin:Q] How to position & color elements...with variables?

Post by peter_h »

I'm just starting out on my "Let's customize a MM5 skin enough to get me off MM4" journey, so I'm still on the "Let's see how much I can get done with just CSS" phase. I'm currently needing some time-saving help with achieving today's aspirations...

Take a look at the following screenshot, which shows what I'm trying to do to the "Album Info View" in the Albums grid view (lvPopupContainer): https://drive.google.com/file/d/11cOS4d ... Lsbd0EnbFS

Some of my top general goals with my skin is to improve...
* efficiency of mouse movement (Reduce the distance the mouse needs to travel to execute the next most-probable click)
* visual efficiency (Put the relevant, grouped info in the place that requires your eyes to move the least; Reduce duplication of info on display)

To that end:

1. How do I position the play/ shuffle control block (Which BTW has no simple identifier in the tag, and seems to only be addressable using the selector div.popupHeader > div.flex.row.left.inline.verticalCenter ), so that it's located underneath the album art in the grid (and also have its solid background obscure the albumTitle/Artist text that's underneath the image)...? (Problem being that the position is not fixed, relative to anything in the lvPopup (AlbumInfo) view -- It depends on the position of the currently selected album in the parent grid matrix.)

2. How do I change the bkd color of the selection highlight surrounding the album art in the grid (div.popupIndicator) to be the same as that selected album's unique color ? (Problem being that it's a different color depending on the selected album [ Cleverly linked to the predominant color of the album art image --- Nice touch, Devs! :D ] )

I've annotated these things with arrows and text, on the screenshot, linked to (above).

Here's some other things I've done to this view, with CSS, so far...

Code: Select all

//-------------------------------- Album View Display Info (lvPopupContainer) ----------------------------
// DONE: Remove album image (redundant); Make header text all one line (compactness); Remove top-right close button (redundant)
// TODO: Move play/shuffle controls to underneath album art; Hide text underneath album art; Make bkd around selected album the same unique color as the info-text bkd

[data-control-class="ArtworkRectangle"] {
    display: none;
}
[data-id="albumTitle"] {
    font-size: 16px  !important;
    margin-left: 20px !important;
}
[data-id="releaseYear"] {
    font-size: 16px  !important;
}
[data-id="albumArtist"] {
    font-size: 16px  !important;
}
.popupHeader br {
    display: none !important;
}
.lvPopupContainer .closebutton {
    display: none;
}
//---------------------------------
Thanks, in advance! :)
:) Pete, from Wellington, New Zealand.

Currently evaluating a migration from MM4 (4.1.31.1919 on Win7 SP1/16Gig RAM/intel) to MM2024 (latest Debug version on Win11 24H2/16GB RAM/intel)

**If you're wondering why I'm still on MM4: It still has more useful-to-me plugins; and I prefer the GUI's responsiveness, panes spreadable over multi-monitors flexibility, and predictability (all coz it's more Windows-native). I also hate "flat design" for its visual inefficiency. For me, MM4's benefits are still > MM5, and MM5's downsides < MM4. There still appears to be way less MM5 plugins -- even after all this time. MM5 docs for plugin developers seem very undeveloped to me -- which makes me uninclined to invest into learning how to improve MM5 for my own wants.
drakinite
Posts: 977
Joined: Tue May 12, 2020 10:06 am
Contact:

Re: [Skin:Q] How to position & color elements...with variables?

Post by drakinite »

Unfortunately, it's not possible to do what you're looking for in pure CSS. You'd need to add some code to templates.js to do what you're looking for.

This was a pretty good opportunity to make a tutorial-esque thing for how to look for what you're trying to do, so while going through this, I left notes of my thought process while doing it. Hopefully this share link will work: https://1drv.ms/o/s!AqHzUrf30uprtJM0ZnP ... A?e=aVdep8

I'd like to use ChatGPT or something to clean up those notes and turn it into mediawiki code to put on the wiki, but I'm tired and will leave that for another day. But here's the resulting code:

templates_add.js

Code: Select all

/// <reference path="C:\\Program Files (x86)\\Ventis\\MediaMonkey\\typescript\\src\\types\\types.ts"/>
/// <reference path="C:\\Program Files (x86)\\Ventis\\MediaMonkey\\typescript\\src\\types\\windowExtensions.ts"/>
/// <reference path="C:\\Program Files (x86)\\Ventis\\MediaMonkey\\typescript\\src\\controls\\control.ts"/>
/// <reference path="C:\\Program Files (x86)\\Ventis\\MediaMonkey\\typescript\\src\\controls\\listview.ts"/>
/// <reference path="C:\\Program Files (x86)\\Ventis\\MediaMonkey\\typescript\\src\\mminit.ts"/>


window.templates.popupRenderers.override({
    album: function ($super, LV, div, item, scrollIntoView, personID) {
        $super.apply(this, [LV, div, item, scrollIntoView, personID]);
        
        /** @type {Control} */
        let divCtrl = div.controlClass;
        
        /** @type {HTMLElement} */
        let popupItem = qe(LV.container, '[data-selected]');
        let imageInfo = qeclass(popupItem, 'imageInfo')[0];
        
        /** @type {HTMLElement} */
        let popupButtons = qe(div, 'div:has( > [data-id=btnPlayPopup])').cloneNode(true);
        let popupButtonsDict = getAllUIElements(popupButtons);
        
        // Create absolute-positioned parent so we can position it over the invisible first/second lines
        let popupButtonsParent = document.createElement('div');
        popupButtonsParent.setAttribute('data-id', 'popupButtonsParent'); // for accessing in the ListView handler
        popupButtonsParent.appendChild(popupButtons);
        // Change the CSS classes so that they are no longer left-aligned
        popupButtons.classList.remove('inline', 'left');
        popupButtons.classList.add('spacearound');

        imageInfo.appendChild(popupButtonsParent);
        
        function clickPlay(e) {
            e.preventDefault();
            e.stopPropagation();
            divCtrl.UI.btnPlayPopup.click();
        }
        function clickShuffle(e) {
            e.preventDefault();
            e.stopPropagation();
            divCtrl.UI.btnShufflePopup.click();
        }
        function clickMenu(e) {
            e.preventDefault();
            e.stopPropagation();
            let event = createNewCustomEvent('mousedown', {});
            divCtrl.UI.btnMenuPopup.dispatchEvent(event);
        }
        divCtrl.localListen(popupButtonsDict.btnPlayPopup, 'click', clickPlay);
        divCtrl.localListen(popupButtonsDict.btnPlayPopup, 'touchend', clickPlay);
        divCtrl.localListen(popupButtonsDict.btnShufflePopup, 'click', clickShuffle);
        divCtrl.localListen(popupButtonsDict.btnShufflePopup, 'touchend', clickShuffle);
        divCtrl.localListen(popupButtonsDict.btnMenuPopup, 'click', clickMenu);
        divCtrl.localListen(popupButtonsDict.btnMenuPopup, 'touchend', clickMenu);
        addEnterAsClick(divCtrl, popupButtonsDict.btnPlayPopup);
        addEnterAsClick(divCtrl, popupButtonsDict.btnShufflePopup);
    },
});

window.templates.override({
    setLVPopupStyles: function($super, img, div, doDullenColor) {
        $super(img, div, doDullenColor);
        
        let LV = div.parentListView;
        let selectedDiv = LV.getDiv(LV.focusedIndex);
        if (!selectedDiv) return console.error('selected div not returned!');
        selectedDiv.style.backgroundColor = div.style.backgroundColor;
        selectedDiv.classList.add('custom-background-color'); // save this so we can retrieve it in showPopup() (we could just as easily use a "data-" attribute but it doesn't really matter what we use)
    }
})

ListView.prototype.override({
    showPopup: function($super, index, isFast) {
        // search to see if the popup button exists anywhere in the list
        let popupButtonsParent = qeid(this.container, 'popupButtonsParent');
        if (popupButtonsParent) {
            popupButtonsParent.parentElement.removeChild(popupButtonsParent);
        }
        // we expect divsWithBG to be length 0 or 1, and a forEach() works just fine for this case. 
        let divsWithBG = qeclass(this.container, 'custom-background-color');
        forEach(divsWithBG, div => {
            div.classList.remove('custom-background-color');
            div.style.backgroundColor = '';
        })
        $super.apply(this, [index, isFast]);
    },
})
and skin_listview_add.less:

Code: Select all

//-------------------------------- Album View Display Info (lvPopupContainer) ----------------------------
// DONE: Remove album image (redundant); Make header text all one line (compactness); Remove top-right close button (redundant)
// TODO: Move play/shuffle controls to underneath album art; Hide text underneath album art; Make bkd around selected album the same unique color as the info-text bkd

[data-control-class="ArtworkRectangle"] {
    display: none;
}
[data-id="albumTitle"] {
    font-size: 16px  !important;
    margin-left: 20px !important;
}
[data-id="releaseYear"] {
    font-size: 16px  !important;
}
[data-id="albumArtist"] {
    font-size: 16px  !important;
}
[data-id="popupHeader"] div:has([data-id="btnPlayPopup"]) {
    display: none;
}
.lvItem.griditem.imageItem .imageInfo:has([data-id=popupButtonsParent]){
    position: relative; // to allow popup buttons to be placed over the first/second lines
    & [data-id=firstLine], & [data-id=secondLine] {
        opacity: 0; // keep their size but just make them invisible
    }
    & [data-id=popupButtonsParent] {
        position: absolute;
        top: 0;
        width: 100%; // allow buttons to stretch to full width of the parent
    }
}
.popupHeader br {
    display: none !important;
}
.lvPopupContainer .closebutton {
    display: none;
}
//---------------------------------

Please let me know if the OneNote share link doesn't work. Enjoy!
Image
Student electrical-computer engineer, web programmer, part-time MediaMonkey developer, full-time MediaMonkey enthusiast
I uploaded many addons to MM's addon page, but not all of those were created by me. "By drakinite, Submitted by drakinite" means I made it on my own time. "By Ventis Media, Inc., Submitted by drakinite" means it may have been made by me or another MediaMonkey developer, so instead of crediting/thanking me, please thank the team. You can still ask me for support on any of our addons.
peter_h
Posts: 146
Joined: Mon Mar 12, 2007 4:38 pm

Re: [Skin:Q] How to position & color elements...with variables?

Post by peter_h »

Wow! Now that's a bloody brilliantly detailed tutorial, Drak! Awesome! :)

Of course -- for the docs project! -- I'm happy to let you know what further questions it brings up for me... ;) ....

e.g. So what's all this about "overides"? How do they work? What can you do with them? How universally applicable are they? What's the overall design of the overide system? What do I have to be careful not to break when I use or misuse them? And -- I didn't realise -- we have to edit .TS files, not the .JS files? Also, "popup" seems to crop up quite a bit. What is MM's exact meaning of it? Is there a consistent naming scheme throughout for it? (A question to be answered in the "Overview" section).

OK, so now we know that we're gonna need a whole other section on getting to know this next "Mods with JS" level, huh? ;)

And yes, I reckon it's worth looking into the AI assistance -- I've been wondering if all of MM's docs, wiki, forum, API, etc could be fed into a "CustomGPT" and be useful? https://www.georgerouse.com/explain/custom-gpt/. I think though, we'd have to be careful that it's fed only MM5-relevant data, not any of the MM4 stuff -- which might confuse things.

// Also, good code commenting BTW. :)
:) Pete, from Wellington, New Zealand.

Currently evaluating a migration from MM4 (4.1.31.1919 on Win7 SP1/16Gig RAM/intel) to MM2024 (latest Debug version on Win11 24H2/16GB RAM/intel)

**If you're wondering why I'm still on MM4: It still has more useful-to-me plugins; and I prefer the GUI's responsiveness, panes spreadable over multi-monitors flexibility, and predictability (all coz it's more Windows-native). I also hate "flat design" for its visual inefficiency. For me, MM4's benefits are still > MM5, and MM5's downsides < MM4. There still appears to be way less MM5 plugins -- even after all this time. MM5 docs for plugin developers seem very undeveloped to me -- which makes me uninclined to invest into learning how to improve MM5 for my own wants.
drakinite
Posts: 977
Joined: Tue May 12, 2020 10:06 am
Contact:

Re: [Skin:Q] How to position & color elements...with variables?

Post by drakinite »

peter_h wrote: Tue Nov 12, 2024 3:27 am Wow! Now that's a bloody brilliantly detailed tutorial, Drak! Awesome! :)

Of course -- for the docs project! -- I'm happy to let you know what further questions it brings up for me... ;) ....
Lovely to hear :slight_smile: and thanks!
peter_h wrote: Tue Nov 12, 2024 3:27 am e.g. So what's all this about "overides"? How do they work? What can you do with them? How universally applicable are they? What's the overall design of the overide system? What do I have to be careful not to break when I use or misuse them?
I've got a little explanation of "overrides" here: https://www.mediamonkey.com/wiki/Import ... )#Override

You can technically access the "override" method on any variable, but it'll only work on objects. The intended use is to let you make modifications to the functions/methods that belong to objects/classes, respectively, e.g. to add code that runs before or after the original code. The idea is to make it easier to make these modifications, so folks don't need to resort to replacing a function completely, by copying the original function implementation and making their modifications that way.
peter_h wrote: Tue Nov 12, 2024 3:27 am And -- I didn't realise -- we have to edit .TS files, not the .JS files?
Ah, I realize I haven't written any explanation about the TS files anywhere in the documentation. The TS (TypeScript) files are the "source code", which can include helpful extra information on variable types and stuff. The API documentation is based on the TS files. It then gets "compiled" into JS (JavaScript), which is run by MM.

I've made a couple attempts at providing an officially supported method of writing addons in TS, complete with type hinting for the whole MM code environment (and then making my pack-mmip tool compile the TS for you before it packages the addon), but haven't had any success, so currently addons need to be written in pure JS.

In other words: The .ts source files are helpful to inspect, but the files that are actually read and executed by MM's engine are .js, so the files you will be writing/editing are JS.
peter_h wrote: Tue Nov 12, 2024 3:27 am Also, "popup" seems to crop up quite a bit. What is MM's exact meaning of it? Is there a consistent naming scheme throughout for it? (A question to be answered in the "Overview" section).
Uuunfortunately, it's not consistent. In most places, "popup" is used to refer to "popup menus", which is MM's name for context menus. Under the hood, it opens these popups in a separate window, similar to dialogs but without any window controls. iirc, Dropdown controls use the same code as popupmenus, i.e. they also open in a separate window. You can inspect all the different windows that MM controls by going to localhost:9222.

In the context of list views, "popup" refers to this, well, popup where you click on an album or artist and then a list of albums or songs appears below where you clicked.

I did a quick cursory search and I didn't see any obvious hints of a third use of the term popup, so I think those are the only two.
peter_h wrote: Tue Nov 12, 2024 3:27 am And yes, I reckon it's worth looking into the AI assistance -- I've been wondering if all of MM's docs, wiki, forum, API, etc could be fed into a "CustomGPT" and be useful? https://www.georgerouse.com/explain/custom-gpt/. I think though, we'd have to be careful that it's fed only MM5-relevant data, not any of the MM4 stuff -- which might confuse things.
Ooooo, this might be pretty good. Though it'd probably be kinda junk if we fed it forum posts, so we'd probably have to wait until we've got much more documentation before making a custom GPT :sweat_smile:
Image
Student electrical-computer engineer, web programmer, part-time MediaMonkey developer, full-time MediaMonkey enthusiast
I uploaded many addons to MM's addon page, but not all of those were created by me. "By drakinite, Submitted by drakinite" means I made it on my own time. "By Ventis Media, Inc., Submitted by drakinite" means it may have been made by me or another MediaMonkey developer, so instead of crediting/thanking me, please thank the team. You can still ask me for support on any of our addons.
Post Reply