新增: 相册展示模块(左侧幻灯片+进度条+目录选择)
- 左侧固定卡片展示照片幻灯片,淡入淡出切换 - 进度条动画显示当前照片剩余时间 - 设置窗口支持选择图片目录和切换间隔(5/10/15/20/30/60秒) - Win32 SHBrowseForFolderW 目录选择对话框 - Go 端管理幻灯片状态,按间隔推送照片 data URI
This commit is contained in:
166
photo.go
Normal file
166
photo.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
photoMu sync.Mutex
|
||||
photoFiles []string
|
||||
photoIdx int
|
||||
photoDir string
|
||||
photoStop chan struct{}
|
||||
photoDone chan struct{}
|
||||
)
|
||||
|
||||
func scanPhotoDir(dir string) []string {
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var files []string
|
||||
for _, e := range entries {
|
||||
if e.IsDir() {
|
||||
continue
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(e.Name()))
|
||||
switch ext {
|
||||
case ".jpg", ".jpeg", ".png", ".bmp", ".webp", ".gif":
|
||||
files = append(files, e.Name())
|
||||
}
|
||||
}
|
||||
sort.Strings(files)
|
||||
return files
|
||||
}
|
||||
|
||||
func photoToDataURI(dir, name string) string {
|
||||
path := filepath.Join(dir, name)
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(name))
|
||||
mime := "image/jpeg"
|
||||
switch ext {
|
||||
case ".png":
|
||||
mime = "image/png"
|
||||
case ".gif":
|
||||
mime = "image/gif"
|
||||
case ".webp":
|
||||
mime = "image/webp"
|
||||
case ".bmp":
|
||||
mime = "image/bmp"
|
||||
}
|
||||
return fmt.Sprintf("data:%s;base64,%s", mime, base64.StdEncoding.EncodeToString(data))
|
||||
}
|
||||
|
||||
func pushCurrentPhoto(interval int) {
|
||||
photoMu.Lock()
|
||||
files := photoFiles
|
||||
idx := photoIdx
|
||||
dir := photoDir
|
||||
photoMu.Unlock()
|
||||
|
||||
if len(files) == 0 || dir == "" {
|
||||
evalJS(`if(window.updatePhotoFromGo) updatePhotoFromGo(null)`)
|
||||
return
|
||||
}
|
||||
if idx >= len(files) {
|
||||
idx = 0
|
||||
}
|
||||
|
||||
src := photoToDataURI(dir, files[idx])
|
||||
if src == "" {
|
||||
return
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(map[string]interface{}{
|
||||
"src": src,
|
||||
"counter": fmt.Sprintf("%d / %d", idx+1, len(files)),
|
||||
"interval": interval,
|
||||
})
|
||||
evalJS(fmt.Sprintf(`if(window.updatePhotoFromGo) updatePhotoFromGo(%s)`, string(data)))
|
||||
}
|
||||
|
||||
func startPhotoLoop() {
|
||||
cfg := loadConfig()
|
||||
if cfg.PhotoDir == "" {
|
||||
return
|
||||
}
|
||||
|
||||
interval := cfg.PhotoInterval
|
||||
if interval <= 0 {
|
||||
interval = 15
|
||||
}
|
||||
|
||||
files := scanPhotoDir(cfg.PhotoDir)
|
||||
if len(files) == 0 {
|
||||
log.Println("相册: 目录为空或无图片")
|
||||
return
|
||||
}
|
||||
|
||||
photoMu.Lock()
|
||||
photoDir = cfg.PhotoDir
|
||||
photoFiles = files
|
||||
photoIdx = 0
|
||||
stop := make(chan struct{})
|
||||
done := make(chan struct{})
|
||||
photoStop = stop
|
||||
photoDone = done
|
||||
photoMu.Unlock()
|
||||
|
||||
log.Printf("相册: 共 %d 张, 间隔 %ds", len(files), interval)
|
||||
pushCurrentPhoto(interval)
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Duration(interval) * time.Second)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
close(done)
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
photoMu.Lock()
|
||||
if len(photoFiles) > 0 {
|
||||
photoIdx = (photoIdx + 1) % len(photoFiles)
|
||||
}
|
||||
photoMu.Unlock()
|
||||
pushCurrentPhoto(interval)
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func stopPhotoLoop() {
|
||||
photoMu.Lock()
|
||||
stop := photoStop
|
||||
done := photoDone
|
||||
photoStop = nil
|
||||
photoDone = nil
|
||||
photoMu.Unlock()
|
||||
|
||||
if stop != nil {
|
||||
close(stop)
|
||||
}
|
||||
if done != nil {
|
||||
<-done
|
||||
}
|
||||
}
|
||||
|
||||
func restartPhotoLoop() {
|
||||
stopPhotoLoop()
|
||||
evalJS(`if(window.updatePhotoFromGo) updatePhotoFromGo(null)`)
|
||||
startPhotoLoop()
|
||||
}
|
||||
Reference in New Issue
Block a user