Closed agjohnson closed 2 weeks ago
So, I thought about this one a bit more today and I lean towards a dynamic implementation using a view, not generating a static number of images.
The reason for this is that for project avatars, we want to always show the GitHub avatar_url first if it exists. This means that this image will always have conditional logic, and I can't always reference that logic from front end code.
However, if this is instead a dynamic view, we can make the decision to redirect the image to the remote repo avatar URL, and if that image isn't defined, show our own generated avatar instead. In this case, I do not need to make a decision inside templates or front end JS whether to show the GitHub avatar or our avatar, I can instead just point to a URL:
<img src="/projects/test-builds/images/avatar/" />
This was already on my radar, as the Dicebear images were a bit of a placeholder, but I got excited about writing some Python for once and landed on something usable:
The images are reproducible SVGs, based on a random seed for the project. I used the Dicebear "initials" avatar SVG source to generate our template. We'd want to cache for some amount of time of course.
The idea would be a project avatar, but also eventually user, team, and organization default avatars.
There is some stuff to talk through in my implementation, so I'll put it up as a draft and we can talk more there.
With an API v3 change, it would be possible to do this all from the front end side instead
This is now possible (see https://github.com/readthedocs/readthedocs.org/issues/10513)
Generating an SVG here is still an option, but I just realized that generating a single sprite image is also an option and one that would be avoid multiple requests and able to be cached too.
The current Dicebear variant is stacked shapes, but other options like zoomed in pixel art could be used here too. The sprite image could be used with or without overlap, giving a fair number of unique combinations for a small image.
Pixel art especially, as we can zoom in instead of using an image with the final aspect ratio. For example, the project avatar images could be 4x4 pixels zoomed to 40x40px. With 50% overlap, a 160x100px sprite image can have ~3800 combinations and would <20Kb uncompressed.
.avatar {
background-image: url("foo.gif");
background-size: 1600px 1000px;
width: 40px;
height: 40px;
image-rendering: pixelated;
}
.avatar.1a {
background-position-x: -800px;
}
.avatar.2b {
background-position-y: -500px;
}
/* <div class="avatar 1a 2b">Project with initials AB</div> */
This is a somewhat fluffy issue, but it would be good to start off with a new implementation to both get rid of the third party dependency but also to implement project/etc icons before going to production. If we wait, project icons will one day automatically switch without the user's input, which would be confusing.
I have a proof of concept implementation that I might try to push forward on this afternoon as a fun project.
This is just using a placeholder 100x100px image for now, so results aren't great yet, and it needs more randomization but, this was easy to put together as a web component:
I really like the project list display with image icons in the view. However, not all projects will have this. We are using the GitHub supplied avatar_url for projects with a connected remote_repo, but for others, we need something generative or a default image.
For now, I'm using Dicebear hosted images, which ultimately are SVGs currently.
In the future, we probably want these on our domain, and it wouldn't be hard to generate these and serve the statically. We probably want to:
{project.pk % 50}.svg
evenWe might also want to do this for default user, team, and organization images. Right now we rely on gravatar for organizations (weird), lots of users have the empty face icon, and we have no visual identifier for teams besides a name.
Update
The PR attached here implements a nice replacement for projects without remote repo avatar URLs, but it did overlook some caching attacks. With an API v3 change, it would be possible to do this all from the front end side instead. I'll wait on this to continue, if someone wants to jump in on that change (it should be small, but I haven't looked yet)