107 lines
2 KiB
Go
107 lines
2 KiB
Go
package theme
|
|
|
|
import (
|
|
"fmt"
|
|
"maps"
|
|
"slices"
|
|
"strings"
|
|
|
|
"forgejo.org/modules/assetfs"
|
|
"forgejo.org/modules/log"
|
|
"forgejo.org/modules/public"
|
|
|
|
"golang.org/x/sync/singleflight"
|
|
)
|
|
|
|
const (
|
|
themeFilenamePrefix = "theme-"
|
|
themeFilenameSuffix = ".css"
|
|
)
|
|
|
|
var (
|
|
group singleflight.Group
|
|
assetFs *assetfs.LayeredFS
|
|
loaded bool
|
|
loadedThemes map[string]Theme // this static variable feels suspicious
|
|
)
|
|
|
|
func GetThemeIds() ([]string, error) {
|
|
themes, err := GetThemes()
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return slices.Collect(maps.Keys(themes)), nil
|
|
}
|
|
|
|
// GetThemes gets the installed Themes.
|
|
//
|
|
// It doesn't install them to settings.UI.Themes.
|
|
func GetThemes() (map[string]Theme, error) {
|
|
if loaded {
|
|
return loadedThemes, nil
|
|
}
|
|
|
|
if assetFs == nil {
|
|
assetFs = public.AssetFS()
|
|
}
|
|
|
|
_, err, _ := group.Do("load-themes", func() (any, error) {
|
|
_themes, err := loadThemes(assetFs)
|
|
|
|
log.Info("Loaded %d themes", len(_themes))
|
|
|
|
if err != nil {
|
|
// Retry on error TODO might want to improve that
|
|
group.Forget("load-themes")
|
|
} else {
|
|
loadedThemes = _themes
|
|
loaded = true
|
|
}
|
|
|
|
return nil, err
|
|
})
|
|
|
|
return loadedThemes, err
|
|
}
|
|
|
|
// loadThemes scans for and loads themes from an asset filesystem.
|
|
//
|
|
// Returns: a map of theme ID: theme metadata.
|
|
func loadThemes(assetFs *assetfs.LayeredFS) (map[string]Theme, error) {
|
|
entries, err := assetFs.ListFiles("assets/css")
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
themes := make(map[string]Theme)
|
|
|
|
for _, entry := range entries {
|
|
if !(strings.HasPrefix(entry, themeFilenamePrefix) && strings.HasSuffix(entry, themeFilenameSuffix)) {
|
|
continue
|
|
}
|
|
|
|
id := entry[len(themeFilenamePrefix) : len(entry) - len(themeFilenameSuffix)]
|
|
|
|
theme := Theme{
|
|
ID: id,
|
|
Name: getFriendlyThemeName(id),
|
|
}
|
|
|
|
if err := loadThemeMeta(assetFs, entry, &theme); err != nil {
|
|
log.Warn("Failed to load meta of theme %s: %s", entry, err)
|
|
}
|
|
|
|
themes[id] = theme
|
|
|
|
log.Debug("Found theme: %s (%s)", theme.Name, id)
|
|
}
|
|
|
|
if len(themes) == 0 {
|
|
return nil, fmt.Errorf("no themes found")
|
|
}
|
|
|
|
return themes, nil
|
|
}
|