205 lines
5.3 KiB
Go
205 lines
5.3 KiB
Go
|
|
package main
|
|||
|
|
|
|||
|
|
// kind_icon.go:启动台可识别 kind 的默认图标(仅本地,不同步;超管维护)。
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"encoding/base64"
|
|||
|
|
"errors"
|
|||
|
|
"fmt"
|
|||
|
|
"strings"
|
|||
|
|
|
|||
|
|
"github.com/wailsapp/wails/v3/pkg/application"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
var knownLaunchKinds = []string{
|
|||
|
|
"node", "go", "python", "java", "php", "dotnet", "exe", "mysql", "redis", "nginx", "web", "other",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func isKnownLaunchKind(k string) bool {
|
|||
|
|
for _, x := range knownLaunchKinds {
|
|||
|
|
if x == k {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (s *Store) ListKindIcons() (map[string]string, error) {
|
|||
|
|
rows, e := s.db.Query(`SELECT kind,value FROM kind_icons WHERE value!=''`)
|
|||
|
|
if e != nil {
|
|||
|
|
return nil, e
|
|||
|
|
}
|
|||
|
|
defer rows.Close()
|
|||
|
|
m := map[string]string{}
|
|||
|
|
for rows.Next() {
|
|||
|
|
var k, v string
|
|||
|
|
if e = rows.Scan(&k, &v); e != nil {
|
|||
|
|
return nil, e
|
|||
|
|
}
|
|||
|
|
m[k] = v
|
|||
|
|
}
|
|||
|
|
return m, rows.Err()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (s *Store) setKindIcon(kind, value string) error {
|
|||
|
|
now := nowRFC()
|
|||
|
|
_, e := s.db.Exec(`INSERT INTO kind_icons(kind,value,updated_at) VALUES(?,?,?)
|
|||
|
|
ON CONFLICT(kind) DO UPDATE SET value=excluded.value,updated_at=excluded.updated_at`, kind, value, now)
|
|||
|
|
return e
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (s *Store) clearKindIcon(kind string) error {
|
|||
|
|
_, e := s.db.Exec(`DELETE FROM kind_icons WHERE kind=?`, kind)
|
|||
|
|
return e
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (a *App) kindIconAdminGate() error {
|
|||
|
|
if a.syncUserID() != festivalAdminID {
|
|||
|
|
return errors.New("KIND_ICON_ADMIN_ONLY")
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ListKindIcons 返回 kind → dataURL。
|
|||
|
|
func (a *App) ListKindIcons() (map[string]string, error) {
|
|||
|
|
if e := a.ready(); e != nil {
|
|||
|
|
return nil, e
|
|||
|
|
}
|
|||
|
|
return a.store.ListKindIcons()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ListKnownLaunchKinds 固定种类列表(前端管理页用)。
|
|||
|
|
func (a *App) ListKnownLaunchKinds() []string {
|
|||
|
|
return append([]string{}, knownLaunchKinds...)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// PickKindIcon 超管为某 kind 选择本地图片(缩放后存 dataURL)。
|
|||
|
|
func (a *App) PickKindIcon(kind string) (string, error) {
|
|||
|
|
if e := a.ready(); e != nil {
|
|||
|
|
return "", e
|
|||
|
|
}
|
|||
|
|
if e := a.kindIconAdminGate(); e != nil {
|
|||
|
|
return "", e
|
|||
|
|
}
|
|||
|
|
kind = strings.TrimSpace(kind)
|
|||
|
|
if !isKnownLaunchKind(kind) {
|
|||
|
|
return "", errors.New("KIND_UNKNOWN")
|
|||
|
|
}
|
|||
|
|
p, e := application.Get().Dialog.OpenFile().
|
|||
|
|
SetTitle(a.localized("选择种类图标", "Choose kind icon")).
|
|||
|
|
AddFilter(a.localized("图片文件", "Image files"), "*.png;*.jpg;*.jpeg;*.gif;*.webp").
|
|||
|
|
PromptForSingleSelection()
|
|||
|
|
if e != nil || strings.TrimSpace(p) == "" {
|
|||
|
|
return "", e
|
|||
|
|
}
|
|||
|
|
data, mime, e := encodeAvatarFile(p)
|
|||
|
|
if e != nil {
|
|||
|
|
return "", e
|
|||
|
|
}
|
|||
|
|
dataURL := fmt.Sprintf("data:%s;base64,%s", mime, base64.StdEncoding.EncodeToString(data))
|
|||
|
|
if e := a.store.setKindIcon(kind, dataURL); e != nil {
|
|||
|
|
return "", e
|
|||
|
|
}
|
|||
|
|
return dataURL, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ClearKindIcon 超管清除某 kind 默认图。
|
|||
|
|
func (a *App) ClearKindIcon(kind string) error {
|
|||
|
|
if e := a.ready(); e != nil {
|
|||
|
|
return e
|
|||
|
|
}
|
|||
|
|
if e := a.kindIconAdminGate(); e != nil {
|
|||
|
|
return e
|
|||
|
|
}
|
|||
|
|
kind = strings.TrimSpace(kind)
|
|||
|
|
if !isKnownLaunchKind(kind) {
|
|||
|
|
return errors.New("KIND_UNKNOWN")
|
|||
|
|
}
|
|||
|
|
return a.store.clearKindIcon(kind)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// PickLaunchIconImage 为启动台选本地图片(base64 dataURL,不强制写库)。
|
|||
|
|
func (a *App) PickLaunchIconImage() (string, error) {
|
|||
|
|
if e := a.ready(); e != nil {
|
|||
|
|
return "", e
|
|||
|
|
}
|
|||
|
|
p, e := application.Get().Dialog.OpenFile().
|
|||
|
|
SetTitle(a.localized("选择应用图标", "Choose app icon")).
|
|||
|
|
AddFilter(a.localized("图片文件", "Image files"), "*.png;*.jpg;*.jpeg;*.gif;*.webp").
|
|||
|
|
PromptForSingleSelection()
|
|||
|
|
if e != nil || strings.TrimSpace(p) == "" {
|
|||
|
|
return "", e
|
|||
|
|
}
|
|||
|
|
data, mime, e := encodeAvatarFile(p)
|
|||
|
|
if e != nil {
|
|||
|
|
return "", e
|
|||
|
|
}
|
|||
|
|
return fmt.Sprintf("data:%s;base64,%s", mime, base64.StdEncoding.EncodeToString(data)), nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SetLaunchAppIcon 写回已保存应用的图标(空字符串=清除)。
|
|||
|
|
func (a *App) SetLaunchAppIcon(id int64, icon string) error {
|
|||
|
|
if e := a.ready(); e != nil {
|
|||
|
|
return e
|
|||
|
|
}
|
|||
|
|
if _, e := a.store.GetLaunchApp(id); e != nil {
|
|||
|
|
return errors.New("LAUNCH_APP_NOT_FOUND")
|
|||
|
|
}
|
|||
|
|
if e := a.store.setLaunchIcon(id, icon); e != nil {
|
|||
|
|
return e
|
|||
|
|
}
|
|||
|
|
a.emit("launchpad:changed", nil)
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// FetchProjectIcon 从项目目录抓取 icon 并写回 projects.icon。
|
|||
|
|
func (a *App) FetchProjectIcon(id int64) (string, error) {
|
|||
|
|
if e := a.ready(); e != nil {
|
|||
|
|
return "", e
|
|||
|
|
}
|
|||
|
|
p, e := a.store.GetProject(id)
|
|||
|
|
if e != nil {
|
|||
|
|
return "", errors.New("PROJECT_NOT_FOUND")
|
|||
|
|
}
|
|||
|
|
u := iconFromDir(p.Path)
|
|||
|
|
if u == "" {
|
|||
|
|
return "", errors.New("ICON_NOT_FOUND")
|
|||
|
|
}
|
|||
|
|
if e := a.store.setProjectIcon(id, u); e != nil {
|
|||
|
|
return "", e
|
|||
|
|
}
|
|||
|
|
return u, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// SetProjectIcon 写入/清除项目图标。
|
|||
|
|
func (a *App) SetProjectIcon(id int64, icon string) error {
|
|||
|
|
if e := a.ready(); e != nil {
|
|||
|
|
return e
|
|||
|
|
}
|
|||
|
|
if _, e := a.store.GetProject(id); e != nil {
|
|||
|
|
return errors.New("PROJECT_NOT_FOUND")
|
|||
|
|
}
|
|||
|
|
return a.store.setProjectIcon(id, icon)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (s *Store) setProjectIcon(id int64, icon string) error {
|
|||
|
|
_, e := s.db.Exec(`UPDATE projects SET icon=?,updated_at=? WHERE id=?`, icon, now(), id)
|
|||
|
|
return e
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// DetectProjectLaunchKind 根据项目路径推断启动台 kind(用于默认图标回退)。
|
|||
|
|
func (a *App) DetectProjectLaunchKind(id int64) (string, error) {
|
|||
|
|
if e := a.ready(); e != nil {
|
|||
|
|
return "", e
|
|||
|
|
}
|
|||
|
|
p, e := a.store.GetProject(id)
|
|||
|
|
if e != nil {
|
|||
|
|
return "", errors.New("PROJECT_NOT_FOUND")
|
|||
|
|
}
|
|||
|
|
prof := detectLaunchProfile(p.Path)
|
|||
|
|
if prof.Kind == "" {
|
|||
|
|
return "other", nil
|
|||
|
|
}
|
|||
|
|
return prof.Kind, nil
|
|||
|
|
}
|