From 34e5df6d300f92bc132df9fceca2f7fc65982c4c Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 10 Mar 2025 15:57:17 +0800 Subject: [PATCH] Add material icons for file list (#33837) --- custom/conf/app.example.ini | 3 + modules/base/tool.go | 22 - modules/fileicon/basic.go | 27 + modules/fileicon/material.go | 150 + modules/reqctx/datastore.go | 5 +- modules/setting/ui.go | 2 + modules/templates/helper.go | 1 - modules/templates/util_render.go | 15 +- modules/templates/util_render_legacy.go | 17 +- modules/templates/util_render_test.go | 23 +- options/fileicon/material-icon-rules.json | 12541 ++++++++++++++++ options/fileicon/material-icon-svgs.json | 1074 ++ package-lock.json | 110 + package.json | 1 + .../img/svg/material-folder-generic.svg | 1 + .../img/svg/material-folder-symlink.svg | 1 + templates/repo/view_list.tmpl | 4 +- tests/integration/repo_test.go | 2 +- tools/generate-svg.js | 61 +- web_src/css/modules/svg.css | 4 + web_src/svg/material-folder-generic.svg | 1 + web_src/svg/material-folder-symlink.svg | 1 + 22 files changed, 13993 insertions(+), 73 deletions(-) create mode 100644 modules/fileicon/basic.go create mode 100644 modules/fileicon/material.go create mode 100644 options/fileicon/material-icon-rules.json create mode 100644 options/fileicon/material-icon-svgs.json create mode 100644 public/assets/img/svg/material-folder-generic.svg create mode 100644 public/assets/img/svg/material-folder-symlink.svg create mode 100644 web_src/svg/material-folder-generic.svg create mode 100644 web_src/svg/material-folder-symlink.svg diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 178d7a1363..0fc49accef 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1294,6 +1294,9 @@ LEVEL = Info ;; Leave it empty to allow users to select any theme from "{CustomPath}/public/assets/css/theme-*.css" ;THEMES = ;; +;; The icons for file list (basic/material), this is a temporary option which will be replaced by a user setting in the future. +;FILE_ICON_THEME = material +;; ;; All available reactions users can choose on issues/prs and comments. ;; Values can be emoji alias (:smile:) or a unicode emoji. ;; For custom reactions, add a tightly cropped square image to public/assets/img/emoji/reaction_name.png diff --git a/modules/base/tool.go b/modules/base/tool.go index b6ed8cbf9a..02ca85569e 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -17,7 +17,6 @@ import ( "strings" "time" - "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -139,24 +138,3 @@ func Int64sToStrings(ints []int64) []string { } return strs } - -// EntryIcon returns the octicon name for displaying files/directories -func EntryIcon(entry *git.TreeEntry) string { - switch { - case entry.IsLink(): - te, err := entry.FollowLink() - if err != nil { - return "file-symlink-file" - } - if te.IsDir() { - return "file-directory-symlink" - } - return "file-symlink-file" - case entry.IsDir(): - return "file-directory-fill" - case entry.IsSubModule(): - return "file-submodule" - } - - return "file" -} diff --git a/modules/fileicon/basic.go b/modules/fileicon/basic.go new file mode 100644 index 0000000000..040a8e87de --- /dev/null +++ b/modules/fileicon/basic.go @@ -0,0 +1,27 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package fileicon + +import ( + "html/template" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/svg" +) + +func BasicThemeIcon(entry *git.TreeEntry) template.HTML { + svgName := "octicon-file" + switch { + case entry.IsLink(): + svgName = "octicon-file-symlink-file" + if te, err := entry.FollowLink(); err == nil && te.IsDir() { + svgName = "octicon-file-directory-symlink" + } + case entry.IsDir(): + svgName = "octicon-file-directory-fill" + case entry.IsSubModule(): + svgName = "octicon-file-submodule" + } + return svg.RenderHTML(svgName) +} diff --git a/modules/fileicon/material.go b/modules/fileicon/material.go new file mode 100644 index 0000000000..54666c76b2 --- /dev/null +++ b/modules/fileicon/material.go @@ -0,0 +1,150 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package fileicon + +import ( + "html/template" + "path" + "strings" + "sync" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/options" + "code.gitea.io/gitea/modules/reqctx" + "code.gitea.io/gitea/modules/svg" +) + +type materialIconRulesData struct { + IconDefinitions map[string]*struct { + IconPath string `json:"iconPath"` + } `json:"iconDefinitions"` + FileNames map[string]string `json:"fileNames"` + FolderNames map[string]string `json:"folderNames"` + FileExtensions map[string]string `json:"fileExtensions"` + LanguageIDs map[string]string `json:"languageIds"` +} + +type MaterialIconProvider struct { + once sync.Once + rules *materialIconRulesData + svgs map[string]string +} + +var materialIconProvider MaterialIconProvider + +func DefaultMaterialIconProvider() *MaterialIconProvider { + return &materialIconProvider +} + +func (m *MaterialIconProvider) loadData() { + buf, err := options.AssetFS().ReadFile("fileicon/material-icon-rules.json") + if err != nil { + log.Error("Failed to read material icon rules: %v", err) + return + } + err = json.Unmarshal(buf, &m.rules) + if err != nil { + log.Error("Failed to unmarshal material icon rules: %v", err) + return + } + + buf, err = options.AssetFS().ReadFile("fileicon/material-icon-svgs.json") + if err != nil { + log.Error("Failed to read material icon rules: %v", err) + return + } + err = json.Unmarshal(buf, &m.svgs) + if err != nil { + log.Error("Failed to unmarshal material icon rules: %v", err) + return + } + log.Debug("Loaded material icon rules and SVG images") +} + +func (m *MaterialIconProvider) renderFileIconSVG(ctx reqctx.RequestContext, name, svg string) template.HTML { + data := ctx.GetData() + renderedSVGs, _ := data["_RenderedSVGs"].(map[string]bool) + if renderedSVGs == nil { + renderedSVGs = make(map[string]bool) + data["_RenderedSVGs"] = renderedSVGs + } + // This part is a bit hacky, but it works really well. It should be safe to do so because all SVG icons are generated by us. + // Will try to refactor this in the future. + if !strings.HasPrefix(svg, "