diff --git a/views/views_image-frame.templ b/views/views_image-frame.templ
new file mode 100644
index 0000000..e2dc0de
--- /dev/null
+++ b/views/views_image-frame.templ
@@ -0,0 +1,27 @@
+package views
+
+import "github.com/damongolding/immich-kiosk/immich"
+
+// frame is a template function that renders a basic frame for an image.
+// It wraps the child content in a div with the class "frame--image".
+templ frame() {
+
+ { children... }
+
+}
+
+// frameWithZoom is a template function that renders a frame with zoom effect for an image.
+// It takes the refresh interval, image effect type, and the image asset as parameters.
+// Depending on the image effect, it applies different CSS classes for zooming.
+templ frameWithZoom(refresh int, imageEffect string, img immich.ImmichAsset) {
+ switch imageEffect {
+ case "smart-zoom":
+
+ { children... }
+
+ default:
+
+ { children... }
+
+ }
+}
diff --git a/views/views_image-frame_templ.go b/views/views_image-frame_templ.go
new file mode 100644
index 0000000..eb60477
--- /dev/null
+++ b/views/views_image-frame_templ.go
@@ -0,0 +1,144 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.778
+package views
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+import "github.com/damongolding/immich-kiosk/immich"
+
+// frame is a template function that renders a basic frame for an image.
+// It wraps the child content in a div with the class "frame--image".
+func frame() templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+// frameWithZoom is a template function that renders a frame with zoom effect for an image.
+// It takes the refresh interval, image effect type, and the image asset as parameters.
+// Depending on the image effect, it applies different CSS classes for zooming.
+func frameWithZoom(refresh int, imageEffect string, img immich.ImmichAsset) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var2 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var2 == nil {
+ templ_7745c5c3_Var2 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ switch imageEffect {
+ case "smart-zoom":
+ var templ_7745c5c3_Var3 = []any{"frame--image", "frame--image-zoom", animationDuration(refresh), zoomInOrOut(imageEffect), smartZoom(img)}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var3...)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ_7745c5c3_Var2.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ default:
+ var templ_7745c5c3_Var5 = []any{"frame--image", "frame--image-zoom", animationDuration(refresh), zoomInOrOut(imageEffect)}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ_7745c5c3_Var2.Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/views/views_image-metadata.templ b/views/views_image-metadata.templ
new file mode 100644
index 0000000..25d2cf1
--- /dev/null
+++ b/views/views_image-metadata.templ
@@ -0,0 +1,122 @@
+package views
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/damongolding/immich-kiosk/config"
+ "github.com/damongolding/immich-kiosk/immich"
+ "github.com/damongolding/immich-kiosk/utils"
+)
+
+// ImageLocation generates a formatted string of the image location based on EXIF information.
+// It combines the city, state, and country information if available.
+func ImageLocation(info immich.ExifInfo) string {
+ var location strings.Builder
+
+ if info.City != "" {
+ location.WriteString(info.City)
+ }
+
+ if info.State != "" {
+ location.WriteString(", ")
+ location.WriteString(info.State)
+ }
+
+ if info.Country != "" {
+ location.WriteString(",
")
+ location.WriteString(info.Country)
+ }
+
+ return location.String()
+}
+
+// ImageExif generates a formatted string of EXIF information for an image.
+// It includes f-number, exposure time, focal length, and ISO if available.
+func ImageExif(info immich.ExifInfo) string {
+ var stats strings.Builder
+
+ if info.FNumber != 0 {
+ stats.WriteString(fmt.Sprintf("ƒ/%.1f", info.FNumber))
+ }
+
+ if info.ExposureTime != "" {
+ if stats.Len() > 0 {
+ stats.WriteString("|")
+ }
+ stats.WriteString(fmt.Sprintf("%ss", info.ExposureTime))
+ }
+
+ if info.FocalLength != 0 {
+ if stats.Len() > 0 {
+ stats.WriteString("|")
+ }
+ stats.WriteString(fmt.Sprintf("%vmm", info.FocalLength))
+ }
+
+ if info.Iso != 0 {
+ if stats.Len() > 0 {
+ stats.WriteString("|")
+ }
+ stats.WriteString(fmt.Sprintf("ISO %v", info.Iso))
+ }
+
+ return stats.String()
+}
+
+// ImageDateTime generates a formatted date and time string for an image based on the view data settings.
+// It can display date, time, or both, in various formats.
+func ImageDateTime(viewData ViewData, imageIndex int) string {
+ var imageDate string
+
+ var imageTimeFormat string
+ if viewData.ImageTimeFormat == "12" {
+ imageTimeFormat = time.Kitchen
+ } else {
+ imageTimeFormat = time.TimeOnly
+ }
+
+ imageDateFormat := utils.DateToLayout(viewData.ImageDateFormat)
+ if imageDateFormat == "" {
+ imageDateFormat = config.DefaultDateLayout
+ }
+
+ switch {
+ case (viewData.ShowImageDate && viewData.ShowImageTime):
+ imageDate = fmt.Sprintf("%s %s", viewData.Images[imageIndex].ImmichImage.LocalDateTime.Format(imageTimeFormat), viewData.Images[imageIndex].ImmichImage.LocalDateTime.Format(imageDateFormat))
+ case viewData.ShowImageDate:
+ imageDate = fmt.Sprintf("%s", viewData.Images[imageIndex].ImmichImage.LocalDateTime.Format(imageDateFormat))
+ case viewData.ShowImageTime:
+ imageDate = fmt.Sprintf("%s", viewData.Images[imageIndex].ImmichImage.LocalDateTime.Format(imageTimeFormat))
+ }
+
+ return imageDate
+}
+
+// imageMetadata renders the metadata for an image, including date, time, EXIF information, location, and ID.
+// The display of each piece of information is controlled by the ViewData settings.
+templ imageMetadata(viewData ViewData, imageIndex int) {
+
+ if viewData.ShowImageDate || viewData.ShowImageTime {
+
+ { ImageDateTime(viewData, imageIndex) }
+
+ }
+ if viewData.ShowImageExif {
+
+ @templ.Raw(ImageExif(viewData.Images[imageIndex].ImmichImage.ExifInfo))
+
+ }
+ if viewData.ShowImageLocation {
+
+ @templ.Raw(ImageLocation(viewData.Images[imageIndex].ImmichImage.ExifInfo))
+
+ }
+ if viewData.ShowImageID {
+
+ { viewData.Images[imageIndex].ImmichImage.ID }
+
+ }
+
+}
diff --git a/views/views_image-metadata_templ.go b/views/views_image-metadata_templ.go
new file mode 100644
index 0000000..4cdd464
--- /dev/null
+++ b/views/views_image-metadata_templ.go
@@ -0,0 +1,224 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.2.778
+package views
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/damongolding/immich-kiosk/config"
+ "github.com/damongolding/immich-kiosk/immich"
+ "github.com/damongolding/immich-kiosk/utils"
+)
+
+// ImageLocation generates a formatted string of the image location based on EXIF information.
+// It combines the city, state, and country information if available.
+func ImageLocation(info immich.ExifInfo) string {
+ var location strings.Builder
+
+ if info.City != "" {
+ location.WriteString(info.City)
+ }
+
+ if info.State != "" {
+ location.WriteString(", ")
+ location.WriteString(info.State)
+ }
+
+ if info.Country != "" {
+ location.WriteString(",
")
+ location.WriteString(info.Country)
+ }
+
+ return location.String()
+}
+
+// ImageExif generates a formatted string of EXIF information for an image.
+// It includes f-number, exposure time, focal length, and ISO if available.
+func ImageExif(info immich.ExifInfo) string {
+ var stats strings.Builder
+
+ if info.FNumber != 0 {
+ stats.WriteString(fmt.Sprintf("ƒ/%.1f", info.FNumber))
+ }
+
+ if info.ExposureTime != "" {
+ if stats.Len() > 0 {
+ stats.WriteString("|")
+ }
+ stats.WriteString(fmt.Sprintf("%ss", info.ExposureTime))
+ }
+
+ if info.FocalLength != 0 {
+ if stats.Len() > 0 {
+ stats.WriteString("|")
+ }
+ stats.WriteString(fmt.Sprintf("%vmm", info.FocalLength))
+ }
+
+ if info.Iso != 0 {
+ if stats.Len() > 0 {
+ stats.WriteString("|")
+ }
+ stats.WriteString(fmt.Sprintf("ISO %v", info.Iso))
+ }
+
+ return stats.String()
+}
+
+// ImageDateTime generates a formatted date and time string for an image based on the view data settings.
+// It can display date, time, or both, in various formats.
+func ImageDateTime(viewData ViewData, imageIndex int) string {
+ var imageDate string
+
+ var imageTimeFormat string
+ if viewData.ImageTimeFormat == "12" {
+ imageTimeFormat = time.Kitchen
+ } else {
+ imageTimeFormat = time.TimeOnly
+ }
+
+ imageDateFormat := utils.DateToLayout(viewData.ImageDateFormat)
+ if imageDateFormat == "" {
+ imageDateFormat = config.DefaultDateLayout
+ }
+
+ switch {
+ case (viewData.ShowImageDate && viewData.ShowImageTime):
+ imageDate = fmt.Sprintf("%s %s", viewData.Images[imageIndex].ImmichImage.LocalDateTime.Format(imageTimeFormat), viewData.Images[imageIndex].ImmichImage.LocalDateTime.Format(imageDateFormat))
+ case viewData.ShowImageDate:
+ imageDate = fmt.Sprintf("%s", viewData.Images[imageIndex].ImmichImage.LocalDateTime.Format(imageDateFormat))
+ case viewData.ShowImageTime:
+ imageDate = fmt.Sprintf("%s", viewData.Images[imageIndex].ImmichImage.LocalDateTime.Format(imageTimeFormat))
+ }
+
+ return imageDate
+}
+
+// imageMetadata renders the metadata for an image, including date, time, EXIF information, location, and ID.
+// The display of each piece of information is controlled by the ViewData settings.
+func imageMetadata(viewData ViewData, imageIndex int) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ var templ_7745c5c3_Var2 = []any{"image--metadata", fmt.Sprintf("image--metadata--theme-%s", viewData.Theme)}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if viewData.ShowImageDate || viewData.ShowImageTime {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var4 string
+ templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(ImageDateTime(viewData, imageIndex))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/views_image-metadata.templ`, Line: 103, Col: 41}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ if viewData.ShowImageExif {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ.Raw(ImageExif(viewData.Images[imageIndex].ImmichImage.ExifInfo)).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ if viewData.ShowImageLocation {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templ.Raw(ImageLocation(viewData.Images[imageIndex].ImmichImage.ExifInfo)).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ if viewData.ShowImageID {
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var5 string
+ templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(viewData.Images[imageIndex].ImmichImage.ID)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/views_image-metadata.templ`, Line: 118, Col: 48}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return templ_7745c5c3_Err
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/views/views_image.templ b/views/views_image.templ
index 3dc2667..718d4d6 100644
--- a/views/views_image.templ
+++ b/views/views_image.templ
@@ -3,14 +3,138 @@ package views
import (
"fmt"
"github.com/charmbracelet/log"
- "github.com/damongolding/immich-kiosk/config"
"github.com/damongolding/immich-kiosk/immich"
"github.com/damongolding/immich-kiosk/utils"
"strings"
- "time"
)
-templ ImageFitCover(ImageData, imageFit string) {
+// Image is the main entry point for rendering images.
+// It determines whether to use a single or split view layout based on the number of images,
+// and renders the history form.
+//
+// Parameters:
+// - viewData: ViewData containing all necessary information for rendering the images.
+templ Image(viewData ViewData) {
+ if len(viewData.Images) < 2 {
+ @layoutSingleView(viewData)
+ } else {
+ @layoutSplitView(viewData)
+ }
+ @renderHistory(viewData)
+}
+
+// layoutSingleView renders a single image layout.
+//
+// Parameters:
+// - viewData: ViewData containing all necessary information for rendering the image.
+templ layoutSingleView(viewData ViewData) {
+ @layoutView(viewData, true)
+}
+
+// layoutSplitView renders a split image layout for multiple images.
+//
+// Parameters:
+// - viewData: ViewData containing all necessary information for rendering the images.
+templ layoutSplitView(viewData ViewData) {
+ @layoutView(viewData, false)
+}
+
+// layoutView renders the layout for either a single image or multiple images.
+// It applies the appropriate CSS classes and renders each image using renderSingleImage.
+//
+// Parameters:
+// - viewData: ViewData containing all necessary information for rendering the images.
+// - isSingle: A boolean indicating whether this is a single image layout.
+templ layoutView(viewData ViewData, isSingle bool) {
+
+ if isSingle {
+ @renderSingleImage(viewData, viewData.Images[0], 0)
+ } else {
+ for imageIndex, imageData := range viewData.Images {
+
+ @renderSingleImage(viewData, imageData, imageIndex)
+
+ }
+ }
+
+}
+
+// renderSingleImage renders a single image with its background and metadata.
+//
+// Parameters:
+// - viewData: ViewData containing rendering settings.
+// - imageData: ImageData for the image to be rendered.
+// - imageIndex: The index of the image in the viewData.Images slice.
+templ renderSingleImage(viewData ViewData, imageData ImageData, imageIndex int) {
+ @renderImageBackground(viewData, imageData)
+ @renderImage(viewData, imageData)
+ if !viewData.DisableUi {
+ @imageMetadata(viewData, imageIndex)
+ }
+}
+
+// renderImageBackground renders a blurred background image if applicable.
+//
+// Parameters:
+// - viewData: ViewData containing background blur settings.
+// - imageData: ImageData containing the blur data for the image.
+templ renderImageBackground(viewData ViewData, imageData ImageData) {
+ if viewData.BackgroundBlur && !strings.EqualFold(viewData.ImageFit, "cover") && len(imageData.ImageBlurData) > 0 {
+
+
+
+ }
+}
+
+// renderImage renders an image with the specified effect and fit.
+// It applies zoom effects if specified, otherwise renders the image with the default frame.
+//
+// Parameters:
+// - viewData: ViewData containing image effect and refresh settings.
+// - imageData: ImageData containing the image data and ImmichImage.
+//
+// The function uses frameWithZoom for zoom effects and frame for default rendering.
+// It delegates to RenderImageWithCoverFit or renderImageFit based on the image effect.
+templ renderImage(viewData ViewData, imageData ImageData) {
+ switch strings.ToLower(viewData.ImageEffect) {
+ case "zoom", "smart-zoom":
+ @frameWithZoom(viewData.Refresh, viewData.ImageEffect, imageData.ImmichImage) {
+ @RenderImageWithCoverFit(imageData.ImageData, viewData.ImageFit)
+ }
+ default:
+ @frame() {
+ @renderImageFit(imageData.ImageData, viewData.ImageFit)
+ }
+ }
+}
+
+// renderImageFit selects and renders the appropriate image fit template based on the imageFit parameter.
+//
+// Parameters:
+// - imageData: A string containing the image data (typically a URL or base64-encoded image).
+// - imageFit: A string specifying the desired image fit style ("cover", "none", or any other value for "contain").
+//
+// The function uses a switch statement to determine which template to use:
+// - "cover": Uses RenderImageWithCoverFit
+// - "none": Uses RenderImageWithoutFit
+// - Any other value: Uses RenderImageWithContainFit (default behavior)
+templ renderImageFit(imageData string, imageFit string) {
+ switch strings.ToLower(imageFit) {
+ case "cover":
+ @RenderImageWithCoverFit(imageData, imageFit)
+ case "none":
+ @RenderImageWithoutFit(imageData, imageFit)
+ default:
+ @RenderImageWithContainFit(imageData, imageFit)
+ }
+}
+
+// RenderImageWithCoverFit renders an image with "cover" fit style.
+//
+// Parameters:
+// - ImageData: A string containing the image data (typically a URL or base64-encoded image).
+// - imageFit: A string specifying the image fit style (unused in this function).
+templ RenderImageWithCoverFit(ImageData, imageFit string) {
}
-templ ImageFitNone(ImageData, imageFit string) {
+// RenderImageWithoutFit renders an image without any specific fit style.
+//
+// Parameters:
+// - ImageData: A string containing the image data (typically a URL or base64-encoded image).
+// - imageFit: A string specifying the image fit style (unused in this function).
+templ RenderImageWithoutFit(ImageData, imageFit string) {
}
-templ ImageFitContain(ImageData, imageFit string) {
+// RenderImageWithContainFit renders an image with "contain" fit style.
+//
+// Parameters:
+// - ImageData: A string containing the image data (typically a URL or base64-encoded image).
+// - imageFit: A string specifying the image fit style (unused in this function).
+templ RenderImageWithContainFit(ImageData, imageFit string) {
}
+// transformOrigin generates a CSS class for the transform-origin property.
+//
+// Parameters:
+// - value: A string representing the desired transform origin value.
+//
+// Returns:
+// - A CSS class for the transform-origin property.
css transformOrigin(value string) {
transform-origin: { value };
}
+// smartZoom calculates the transform origin for an image based on detected faces.
+// It returns a templ.CSSClass for the transform-origin CSS property.
+//
+// If no faces are detected, it returns a random corner as the transform origin.
+// If faces are detected, it uses the center point of all faces as the transform origin.
+//
+// Parameters:
+// - image: An immich.ImmichAsset containing information about detected faces.
+//
+// Returns:
+// - templ.CSSClass: A CSS class for the transform-origin property.
func smartZoom(image immich.ImmichAsset) templ.CSSClass {
-
if len(image.People) == 0 && len(image.UnassignedFaces) == 0 {
log.Info("No FACES", "ID", image.ID)
return transformOrigin(fmt.Sprintf("%s %s", utils.RandomItem([]string{"top", "bottom"}), utils.RandomItem([]string{"left", "right"})))
@@ -52,203 +203,13 @@ func smartZoom(image immich.ImmichAsset) templ.CSSClass {
}
return transformOrigin(fmt.Sprintf("%f%% %f%%", x, y))
-
-}
-
-func ImageLocation(info immich.ExifInfo) string {
- var location strings.Builder
-
- if info.City != "" {
- location.WriteString(info.City)
- }
-
- if info.State != "" {
- location.WriteString(", ")
- location.WriteString(info.State)
- }
-
- if info.Country != "" {
- location.WriteString(",
")
- location.WriteString(info.Country)
- }
-
- return location.String()
-}
-
-func ImageExif(info immich.ExifInfo) string {
- var stats strings.Builder
-
- if info.FNumber != 0 {
- stats.WriteString(fmt.Sprintf("ƒ/%.1f", info.FNumber))
- }
-
- if info.ExposureTime != "" {
- if stats.Len() > 0 {
- stats.WriteString("|")
- }
- stats.WriteString(fmt.Sprintf("%ss", info.ExposureTime))
- }
-
- if info.FocalLength != 0 {
- if stats.Len() > 0 {
- stats.WriteString("|")
- }
- stats.WriteString(fmt.Sprintf("%vmm", info.FocalLength))
- }
-
- if info.Iso != 0 {
- if stats.Len() > 0 {
- stats.WriteString("|")
- }
- stats.WriteString(fmt.Sprintf("ISO %v", info.Iso))
- }
-
- return stats.String()
}
-func ImageDateTime(viewData ViewData, imageIndex int) string {
- var imageDate string
-
- var imageTimeFormat string
- if viewData.ImageTimeFormat == "12" {
- imageTimeFormat = time.Kitchen
- } else {
- imageTimeFormat = time.TimeOnly
- }
-
- imageDateFormat := utils.DateToLayout(viewData.ImageDateFormat)
- if imageDateFormat == "" {
- imageDateFormat = config.DefaultDateLayout
- }
-
- switch {
- case (viewData.ShowImageDate && viewData.ShowImageTime):
- imageDate = fmt.Sprintf("%s %s", viewData.Images[imageIndex].ImmichImage.LocalDateTime.Format(imageTimeFormat), viewData.Images[imageIndex].ImmichImage.LocalDateTime.Format(imageDateFormat))
- case viewData.ShowImageDate:
- imageDate = fmt.Sprintf("%s", viewData.Images[imageIndex].ImmichImage.LocalDateTime.Format(imageDateFormat))
- case viewData.ShowImageTime:
- imageDate = fmt.Sprintf("%s", viewData.Images[imageIndex].ImmichImage.LocalDateTime.Format(imageTimeFormat))
- }
-
- return imageDate
-}
-
-templ imageMetadata(viewData ViewData, imageIndex int) {
-
- if viewData.ShowImageDate || viewData.ShowImageTime {
-
- { ImageDateTime(viewData, imageIndex) }
-
- }
- if viewData.ShowImageExif {
-
- @templ.Raw(ImageExif(viewData.Images[imageIndex].ImmichImage.ExifInfo))
-
- }
- if viewData.ShowImageLocation {
-
- @templ.Raw(ImageLocation(viewData.Images[imageIndex].ImmichImage.ExifInfo))
-
- }
- if viewData.ShowImageID {
-
- { viewData.Images[imageIndex].ImmichImage.ID }
-
- }
-
-}
-
-templ frame() {
-
- { children... }
-
-}
-
-templ frameWithZoom(refresh int, imageEffect string, img immich.ImmichAsset) {
- switch imageEffect {
- case "smart-zoom":
-
- { children... }
-
- default:
-
- { children... }
-
- }
-}
-
-templ layoutSingleView(viewData ViewData) {
-
- if viewData.BackgroundBlur && !strings.EqualFold(viewData.ImageFit, "cover") && len(viewData.Images[0].ImageBlurData) > 0 {
-
-
-
- }
- switch strings.ToLower(viewData.ImageEffect) {
- case "zoom", "smart-zoom":
- @frameWithZoom(viewData.Refresh, viewData.ImageEffect, viewData.Images[0].ImmichImage) {
- @ImageFitCover(viewData.Images[0].ImageData, viewData.ImageFit)
- }
- default:
- @frame() {
- switch strings.ToLower(viewData.ImageFit) {
- case "cover":
- @ImageFitCover(viewData.Images[0].ImageData, viewData.ImageFit)
- case "none":
- @ImageFitNone(viewData.Images[0].ImageData, viewData.ImageFit)
- default:
- @ImageFitContain(viewData.Images[0].ImageData, viewData.ImageFit)
- }
- }
- }
- if !viewData.DisableUi {
- if !viewData.DisableUi {
- @imageMetadata(viewData, 0)
- }
- }
-
-}
-
-templ layoutSplitView(viewData ViewData) {
-
- for imageIndex, imageData := range viewData.Images {
-
- if viewData.BackgroundBlur && !strings.EqualFold(viewData.ImageFit, "cover") && len(imageData.ImageBlurData) > 0 {
-
-
-
- }
- switch strings.ToLower(viewData.ImageEffect) {
- case "zoom", "smart-zoom":
- @frameWithZoom(viewData.Refresh, viewData.ImageEffect, viewData.Images[imageIndex].ImmichImage) {
- @ImageFitCover(imageData.ImageData, viewData.ImageFit)
- }
- default:
- @frame() {
- switch strings.ToLower(viewData.ImageFit) {
- case "cover":
- @ImageFitCover(imageData.ImageData, viewData.ImageFit)
- case "none":
- @ImageFitNone(imageData.ImageData, viewData.ImageFit)
- default:
- @ImageFitContain(imageData.ImageData, viewData.ImageFit)
- }
- }
- }
- if !viewData.DisableUi {
- @imageMetadata(viewData, imageIndex)
- }
-
- }
-
-}
-
-templ Image(viewData ViewData) {
- if len(viewData.Images) < 2 {
- @layoutSingleView(viewData)
- } else {
- @layoutSplitView(viewData)
- }
+// renderHistory renders a form containing the viewing history of images.
+//
+// Parameters:
+// - viewData: ViewData containing the history and current images.
+templ renderHistory(viewData ViewData) {