Questions about custom conference theme plugin development

Hello, and thanks for all your hard work on Indico. We at Canonical are deploying Indico as a solution for all of our internal and community events, but we would like to be able to customize the look and feel of the conference pages beyond what the “custom css upload” can do. Housed inside a plugin we want to create a suite of conference themes for our different events that event managers can select from the “themes” dropdown in the admin side of the conference.

At first I tried using the lcagenda themes plugin as a template, but that only seems to affect meeting/timetables, rather than the look and feel of conferences, and our custom themes only showed in the timetable theme dropdown, not the “conference theme” dropdown.

After digging through the code, I found the “get_conference_themes” signal that allows me to connect my own function where I can add references to custom name/css/title tuples which do show up as custom themes in the dropdown list, which is 80% of what I’m looking for. However, it seems like we can only specify a specific, single css file that encapsulates the entirety of the “theme”, rather than being able to define templates/javascript as well to accompany the css. Is this the case? Is there any way that we can also specify, at the very least, a custom javascript file that can also become activated when selecting the theme? We’d like to do more than just modify the css, and I’d rather not have to apply the js changes site-wide, using logic to determine if it executes or not. Having that tied to the same dropdown “theme” option would be amazing.

Any guidance you can give me would be great. Hopefully you can look at our repo above and let us know if we’re on the right track. Also, as a point of feedback, it’s very confusing have two different “theme” systems, one for meetings/timetables which sources from the .json file and a totally different one for conferences that you need to pass in as tuples. It’s not clear why the first one won’t work for conference themes as well, at least intuitively.

Here’s a snippet from an internal plugin we have that uses this signal:

    def init(self):
        super().init()
        self.connect(signals.plugin.get_conference_themes, self._get_conference_themes)

    def get_blueprints(self):
        from indico_psi.blueprint import blueprint
        return blueprint

    def _get_conference_themes(self, sender, **kwargs):
        yield 'psi-classic', 'psi-classic.css', 'PSI Classic'
        yield 'psi-color', 'psi-color.css', 'PSI Color'

The css files are simply in the plugin’s static folder. So I think your problem may be that you use /css/... in the returned data instead of just the file name (I never tested it with subfolders, it may work, but maybe not with a leading slash)

Oh I have no issue getting the css to work and load. My code is basically the same as yours, the only difference being I iterate over my list of tuples in a loop, yielding each. Having the css in a subfolder works perfectly fine.

My issue was about being limited to just the .css file for customization. Can we also specify a js file and or templates to accompany the css when choosing the dropdown option? I didn’t see a way to do it in the documentation anyhow.

Ah true, I should have fully read your post :wink: You cannot include templates/js there (so you cannot easily do this conditionally depending on the selected theme), but you could inject templates into certain pages (self.inject_bundle()); regarding templates it depends on what you want to do - you can either override some templates or use existing template hooks to insert additional data in some place.

Yeah I knew that I could use self.inject_bundle(), or just put the js in the CUSTOMIZATION_DIR, and I have done so in the past, but the issue there is the javascript is loaded on each and every page (or at least it was how I did it), and I had to wrap my code in big conditionals that looked for a specific event title (or some other handle) to execute or not. This is fine for a few events, but it gets messy as we push this to scale. Ideally we want there to be many, many of these events happening, and I don’t want to have insane amounts of hard coded conditionals in my javascript statements. A much more elegant solution, for example, would be a 4th item within the tuples that specifies a .js file that can optionally be loaded alongside the .css file. That way the javascript is encapsulated to only the conferences that select a specific theme, and in that .js file I can have the freedom to add and modify html elements on the page.

Is this something that is a hard no for a future addition? It seems like a reasonable thing to want to have when making a custom theme for conferences lol.

but the issue there is the javascript is loaded on each and every page

You can provide a custom condition in inject_bundle() and use e.g. g.rh.event to access the event (and its selected theme). But of course you’d need one bundle per theme for this to work (not necessarily a problem, a plugin’s webpack-bundles.json can define more than one bundle), and it’s still a bit hacky.

A much more elegant solution, for example, would be a 4th item within the tuples that specifies a .js file that can optionally be loaded alongside the .css file

Would you consider contributing this? I think an option to add JS (just a plain static file, no bundling etc) via the theme system would be nice, but it’s not something we need ourselves. Considering that this would be somewhat simple, there’s a good chance it’d still make it into the 3.2 release (or 3.2.1 depending on the timing).

Regarding the implementation, instead of just looking at the tuple size, maybe it’d be nicer to add a dataclass for this (but still support the tuple and just populate the dataclass from it in that case). I did exactly that for template hooks not too long ago (look for the TemplateSnippet class in the core).

Absolutely, I’d be more than willing to send over a patch. Granted, I’m learning all of this for the first time trying to get this theme plugin made, so pardon any mistakes on my part. It takes me a while to get my head wrapped around big codebases like Indico.

And yeah, my thought was exactly that, just to have the option of adding a simple, static .js per theme entry, so we at least have the option of some more advanced logic if we choose. I also like the idea moving away from the tuple to a dataclass, but that might be a bit over my head, particularly with keeping the legacy functionality in tact. I can try it, though.

For the legacy handling you’d just need something like this when going over the themes coming from the signal:

x if isinstance(x, ConferenceTheme) else ConferenceTheme(*x)

The current code to get the theme list is this:

def get_plugin_conference_themes():
    data = values_from_signal(signals.plugin.get_conference_themes.send(), return_plugins=True)
    return {':'.join((plugin.name, name)): (path, title) for plugin, (name, path, title) in data}

so it could be written like this (completely untested):

def get_plugin_conference_themes():
    data = values_from_signal(signals.plugin.get_conference_themes.send(), return_plugins=True)
    return {
        ':'.join((plugin.name, name)): (theme_info if isinstance(theme_info, ConferenceTheme)
                                        else ConferenceTheme(*theme_info))
        for plugin, theme_info in data
    }

And right below that function there’s the other code related to it, which should be somewhat straightforward to adapt.

Awesome thanks for the head start. I’m setting up a build environment now. It always takes a minute to get to a place where I have a working version

alright, I took a stab at it. This first attempt is still using tuples as before, I figure I’d do one thing at a time. It seems to work on my end, but I’m sure I probably missed something important.

If you could review this, that’d be awesome. I’ll wait to do the PR until it’s actually complete

Ok, I think I did it. I have the new ConferenceTheme dataclass implemented and tested, and it’s working as far as I can tell. The only question I had was where to put the class definition, but I ended up just putting it in the indico/modules/events/layout/util.py where the other functions were located.

I think it’s all done, and I tested it pretty thoroughly with 3 element tuples, 4 element tuples, and the new dataclass, and mixtures therein, and it all seemed to work. Sending over the PR now for your review.