package api import ( "errors" "fmt" "log" "net/http" "os" "path/filepath" "runtime" "strings" ) type uiDesignAssets struct { RootPath string CSSPath string JSPath string } func registerUIDesignRoutes(mux *http.ServeMux) { assets, err := resolveUIDesignAssets() if err != nil { log.Printf("ui design assets: %v", err) return } log.Printf("ui design assets: using %s", assets.RootPath) mux.HandleFunc("/ui-design/static/css/theme.css", func(w http.ResponseWriter, r *http.Request) { serveUIDesignFile(w, r, "/ui-design/static/css/theme.css", assets.CSSPath, "text/css; charset=utf-8") }) if assets.JSPath != "" { mux.HandleFunc("/ui-design/static/js/app.js", func(w http.ResponseWriter, r *http.Request) { serveUIDesignFile(w, r, "/ui-design/static/js/app.js", assets.JSPath, "application/javascript; charset=utf-8") }) } } func serveUIDesignFile(w http.ResponseWriter, r *http.Request, routePath, filePath, contentType string) { if r.URL.Path != routePath { http.NotFound(w, r) return } if r.Method != http.MethodGet && r.Method != http.MethodHead { w.WriteHeader(http.StatusMethodNotAllowed) return } if _, err := os.Stat(filePath); err != nil { http.NotFound(w, r) return } w.Header().Set("Cache-Control", "no-store") w.Header().Set("Content-Type", contentType) http.ServeFile(w, r, filePath) } func resolveUIDesignAssets() (uiDesignAssets, error) { roots := candidateUIDesignRoots() if len(roots) == 0 { return uiDesignAssets{}, errors.New("no ui-design-code path candidates") } cssSuffixes := []string{ filepath.Join("demo", "web", "static", "css", "app.css"), filepath.Join("kit", "patterns", "theme-vapor", "static", "vapor.css"), filepath.Join("kit", "patterns", "theme-aqua-legacy", "demo-aqua-freeze.css"), } jsSuffixes := []string{ filepath.Join("demo", "web", "static", "js", "app.js"), } for _, root := range roots { cssPath := firstExistingFile(root, cssSuffixes) if cssPath == "" { continue } jsPath := firstExistingFile(root, jsSuffixes) return uiDesignAssets{ RootPath: root, CSSPath: cssPath, JSPath: jsPath, }, nil } return uiDesignAssets{}, fmt.Errorf("ui-design-code not found in candidates: %s", strings.Join(roots, ", ")) } func candidateUIDesignRoots() []string { seen := make(map[string]struct{}) var roots []string push := func(path string) { if strings.TrimSpace(path) == "" { return } abs, err := filepath.Abs(path) if err != nil { return } abs = filepath.Clean(abs) if _, ok := seen[abs]; ok { return } info, err := os.Stat(abs) if err != nil || !info.IsDir() { return } seen[abs] = struct{}{} roots = append(roots, abs) } push(os.Getenv("UI_DESIGN_CODE_DIR")) push(filepath.Join("tools", "ui-design-code")) push(filepath.Join("..", "ui-design-code")) if _, file, _, ok := runtime.Caller(0); ok { apiDir := filepath.Dir(file) coreRoot := filepath.Clean(filepath.Join(apiDir, "..", "..")) push(filepath.Join(coreRoot, "tools", "ui-design-code")) push(filepath.Join(coreRoot, "..", "ui-design-code")) } return roots } func firstExistingFile(root string, suffixes []string) string { for _, suffix := range suffixes { path := filepath.Join(root, suffix) info, err := os.Stat(path) if err == nil && !info.IsDir() { return path } } return "" }