diff --git a/assets/static/img/lens-114.png b/assets/static/img/lens-114.png new file mode 100755 index 000000000..0484b138a Binary files /dev/null and b/assets/static/img/lens-114.png differ diff --git a/assets/static/img/lens-128.png b/assets/static/img/lens-128.png new file mode 100755 index 000000000..b07bb893c Binary files /dev/null and b/assets/static/img/lens-128.png differ diff --git a/assets/static/img/lens-144.png b/assets/static/img/lens-144.png new file mode 100755 index 000000000..01cbc84d0 Binary files /dev/null and b/assets/static/img/lens-144.png differ diff --git a/assets/static/img/lens-152.png b/assets/static/img/lens-152.png new file mode 100755 index 000000000..061631e39 Binary files /dev/null and b/assets/static/img/lens-152.png differ diff --git a/assets/static/img/lens-16.png b/assets/static/img/lens-16.png new file mode 100755 index 000000000..adc70f7d9 Binary files /dev/null and b/assets/static/img/lens-16.png differ diff --git a/assets/static/img/lens-160.png b/assets/static/img/lens-160.png new file mode 100755 index 000000000..9a8d4d042 Binary files /dev/null and b/assets/static/img/lens-160.png differ diff --git a/assets/static/img/lens-167.png b/assets/static/img/lens-167.png new file mode 100755 index 000000000..860d04a92 Binary files /dev/null and b/assets/static/img/lens-167.png differ diff --git a/assets/static/img/lens-180.png b/assets/static/img/lens-180.png new file mode 100755 index 000000000..011a22043 Binary files /dev/null and b/assets/static/img/lens-180.png differ diff --git a/assets/static/img/lens-192.png b/assets/static/img/lens-192.png new file mode 100755 index 000000000..8f426ae0d Binary files /dev/null and b/assets/static/img/lens-192.png differ diff --git a/assets/static/img/lens-196.png b/assets/static/img/lens-196.png new file mode 100755 index 000000000..32a7abb79 Binary files /dev/null and b/assets/static/img/lens-196.png differ diff --git a/assets/static/img/lens-256.png b/assets/static/img/lens-256.png new file mode 100755 index 000000000..cb7cecb6f Binary files /dev/null and b/assets/static/img/lens-256.png differ diff --git a/assets/static/img/lens-32.png b/assets/static/img/lens-32.png new file mode 100755 index 000000000..870784f90 Binary files /dev/null and b/assets/static/img/lens-32.png differ diff --git a/assets/static/img/lens-400.png b/assets/static/img/lens-400.png new file mode 100755 index 000000000..938c28c6e Binary files /dev/null and b/assets/static/img/lens-400.png differ diff --git a/assets/static/img/lens-512.png b/assets/static/img/lens-512.png new file mode 100755 index 000000000..77864247c Binary files /dev/null and b/assets/static/img/lens-512.png differ diff --git a/assets/static/img/lens-72.png b/assets/static/img/lens-72.png new file mode 100755 index 000000000..605db7e34 Binary files /dev/null and b/assets/static/img/lens-72.png differ diff --git a/assets/templates/manifest.json b/assets/templates/manifest.json index a7382c38f..70ecf331e 100644 --- a/assets/templates/manifest.json +++ b/assets/templates/manifest.json @@ -1,87 +1,87 @@ { - "short_name": "{{ .config.SiteTitle }}", - "name": "{{ .config.SiteTitle }}", + "name": "{{ .config.AppName }}", + "short_name": "{{ printf "%.12s" .config.AppName }}", "description": "{{ .config.SiteDescription }}", "icons": [ { - "src": "{{ .config.StaticUri }}/img/favicon-16.png", + "src": "{{ .config.StaticUri }}/img/{{ .config.AppIcon }}-16.png", "sizes": "16x16", "type": "image/png" }, { - "src": "{{ .config.StaticUri }}/img/favicon-32.png", + "src": "{{ .config.StaticUri }}/img/{{ .config.AppIcon }}-32.png", "sizes": "32x32", "type": "image/png" }, { - "src": "{{ .config.StaticUri }}/img/favicon-72.png", + "src": "{{ .config.StaticUri }}/img/{{ .config.AppIcon }}-72.png", "sizes": "72x72", "type": "image/png" }, { - "src": "{{ .config.StaticUri }}/img/favicon-114.png", + "src": "{{ .config.StaticUri }}/img/{{ .config.AppIcon }}-114.png", "sizes": "114x114", "type": "image/png" }, { - "src": "{{ .config.StaticUri }}/img/favicon-128.png", + "src": "{{ .config.StaticUri }}/img/{{ .config.AppIcon }}-128.png", "sizes": "128x128", "type": "image/png" }, { - "src": "{{ .config.StaticUri }}/img/favicon-144.png", + "src": "{{ .config.StaticUri }}/img/{{ .config.AppIcon }}-144.png", "sizes": "144x144", "type": "image/png" }, { - "src": "{{ .config.StaticUri }}/img/favicon-152.png", + "src": "{{ .config.StaticUri }}/img/{{ .config.AppIcon }}-152.png", "sizes": "152x152", "type": "image/png" }, { - "src": "{{ .config.StaticUri }}/img/favicon-160.png", + "src": "{{ .config.StaticUri }}/img/{{ .config.AppIcon }}-160.png", "sizes": "160x160", "type": "image/png" }, { - "src": "{{ .config.StaticUri }}/img/favicon-167.png", + "src": "{{ .config.StaticUri }}/img/{{ .config.AppIcon }}-167.png", "sizes": "167x167", "type": "image/png" }, { - "src": "{{ .config.StaticUri }}/img/favicon-180.png", + "src": "{{ .config.StaticUri }}/img/{{ .config.AppIcon }}-180.png", "sizes": "180x180", "type": "image/png" }, { - "src": "{{ .config.StaticUri }}/img/favicon-192.png", + "src": "{{ .config.StaticUri }}/img/{{ .config.AppIcon }}-192.png", "sizes": "192x192", "type": "image/png" }, { - "src": "{{ .config.StaticUri }}/img/favicon-196.png", + "src": "{{ .config.StaticUri }}/img/{{ .config.AppIcon }}-196.png", "sizes": "196x196", "type": "image/png" }, { - "src": "{{ .config.StaticUri }}/img/favicon-256.png", + "src": "{{ .config.StaticUri }}/img/{{ .config.AppIcon }}-256.png", "sizes": "256x256", "type": "image/png" }, { - "src": "{{ .config.StaticUri }}/img/favicon-400.png", + "src": "{{ .config.StaticUri }}/img/{{ .config.AppIcon }}-400.png", "sizes": "400x400", "type": "image/png" }, { - "src": "{{ .config.StaticUri }}/img/favicon-512.png", + "src": "{{ .config.StaticUri }}/img/{{ .config.AppIcon }}-512.png", "sizes": "512x512", "type": "image/png" } ], "scope": "{{ .config.BaseUri }}/", "start_url": "{{ .config.BaseUri }}/", - "display": "standalone", + "display": "{{ .config.AppMode }}", "theme_color": "#0d0d0d", "background_color": "#0d0d0d", "permissions": [ diff --git a/docker-compose.yml b/docker-compose.yml index 850f23d10..61af7c38e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,7 +27,7 @@ services: PHOTOPRISM_SITE_URL: "http://localhost:2342/" PHOTOPRISM_SITE_TITLE: "PhotoPrism" PHOTOPRISM_SITE_CAPTION: "Browse Your Life" - PHOTOPRISM_SITE_DESCRIPTION: "Open-Source Photo Management" + PHOTOPRISM_SITE_DESCRIPTION: "AI-powered app for browsing, organizing & sharing your photo collection." PHOTOPRISM_SITE_AUTHOR: "@photoprism_app" PHOTOPRISM_DEBUG: "true" PHOTOPRISM_READONLY: "false" diff --git a/docker/demo/Dockerfile b/docker/demo/Dockerfile index 6b48eca46..e78f2e0f5 100644 --- a/docker/demo/Dockerfile +++ b/docker/demo/Dockerfile @@ -16,9 +16,11 @@ ENV TF_CPP_MIN_LOG_LEVEL=2 \ PHOTOPRISM_THUMB_SIZE_UNCACHED=4096 \ PHOTOPRISM_JPEG_SIZE=4096 \ PHOTOPRISM_JPEG_QUALITY=95 \ - PHOTOPRISM_SITE_TITLE="Demo" \ - PHOTOPRISM_SITE_CAPTION="PhotoPrism" \ - PHOTOPRISM_SITE_DESCRIPTION="Open-Source Photo Management. Say goodbye to uploading your visual memories to the cloud!" + PHOTOPRISM_APP_NAME="Demo" \ + PHOTOPRISM_APP_ICON="favicon" \ + PHOTOPRISM_SITE_TITLE="PhotoPrism" \ + PHOTOPRISM_SITE_CAPTION="Demo" \ + PHOTOPRISM_SITE_DESCRIPTION="AI-powered app for browsing, organizing & sharing your photo collection. Tags and finds pictures without getting in your way." # Copy assets COPY /docker/demo/index.tmpl /photoprism/assets/templates diff --git a/internal/commands/config.go b/internal/commands/config.go index c994f281c..77bf8ee61 100644 --- a/internal/commands/config.go +++ b/internal/commands/config.go @@ -26,7 +26,7 @@ func configAction(ctx *cli.Context) error { fmt.Printf("%-25s VALUE\n", "NAME") - // Feature flags and auth. + // Flags. fmt.Printf("%-25s %t\n", "debug", conf.Debug()) fmt.Printf("%-25s %s\n", "log-level", conf.LogLevel()) fmt.Printf("%-25s %s\n", "log-filename", conf.LogFilename()) @@ -35,12 +35,12 @@ func configAction(ctx *cli.Context) error { fmt.Printf("%-25s %t\n", "read-only", conf.ReadOnly()) fmt.Printf("%-25s %t\n", "experimental", conf.Experimental()) - // Config path and main file. + // Config. fmt.Printf("%-25s %s\n", "config-file", conf.ConfigFile()) fmt.Printf("%-25s %s\n", "config-path", conf.ConfigPath()) fmt.Printf("%-25s %s\n", "settings-file", conf.SettingsFile()) - // Main directories. + // Paths. fmt.Printf("%-25s %s\n", "originals-path", conf.OriginalsPath()) fmt.Printf("%-25s %d\n", "originals-limit", conf.OriginalsLimit()) fmt.Printf("%-25s %s\n", "import-path", conf.ImportPath()) @@ -52,19 +52,19 @@ func configAction(ctx *cli.Context) error { fmt.Printf("%-25s %s\n", "backup-path", conf.BackupPath()) fmt.Printf("%-25s %s\n", "assets-path", conf.AssetsPath()) - // Asset path and file names. + // Assets. fmt.Printf("%-25s %s\n", "static-path", conf.StaticPath()) fmt.Printf("%-25s %s\n", "build-path", conf.BuildPath()) fmt.Printf("%-25s %s\n", "img-path", conf.ImgPath()) fmt.Printf("%-25s %s\n", "templates-path", conf.TemplatesPath()) - // Background workers. + // Workers. fmt.Printf("%-25s %d\n", "workers", conf.Workers()) fmt.Printf("%-25s %d\n", "wakeup-interval", conf.WakeupInterval()/time.Second) fmt.Printf("%-25s %d\n", "auto-index", conf.AutoIndex()/time.Second) fmt.Printf("%-25s %d\n", "auto-import", conf.AutoImport()/time.Second) - // Disable features. + // Features. fmt.Printf("%-25s %t\n", "disable-backups", conf.DisableBackups()) fmt.Printf("%-25s %t\n", "disable-settings", conf.DisableSettings()) fmt.Printf("%-25s %t\n", "disable-places", conf.DisablePlaces()) @@ -78,31 +78,38 @@ func configAction(ctx *cli.Context) error { fmt.Printf("%-25s %t\n", "disable-heifconvert", conf.DisableHeifConvert()) fmt.Printf("%-25s %t\n", "disable-ffmpeg", conf.DisableFFmpeg()) - // Everything related to TensorFlow. + // TensorFlow. fmt.Printf("%-25s %s\n", "tensorflow-version", conf.TensorFlowVersion()) fmt.Printf("%-25s %s\n", "tensorflow-model-path", conf.TensorFlowModelPath()) fmt.Printf("%-25s %t\n", "detect-nsfw", conf.DetectNSFW()) fmt.Printf("%-25s %t\n", "upload-nsfw", conf.UploadNSFW()) - // Site information. + // Site. fmt.Printf("%-25s %s\n", "site-url", conf.SiteUrl()) - fmt.Printf("%-25s %s\n", "site-preview", conf.SitePreview()) fmt.Printf("%-25s %s\n", "site-author", conf.SiteAuthor()) fmt.Printf("%-25s %s\n", "site-title", conf.SiteTitle()) fmt.Printf("%-25s %s\n", "site-caption", conf.SiteCaption()) fmt.Printf("%-25s %s\n", "site-description", conf.SiteDescription()) + fmt.Printf("%-25s %s\n", "site-preview", conf.SitePreview()) + + // Progressive Web App. + fmt.Printf("%-25s %s\n", "app-name", conf.AppName()) + fmt.Printf("%-25s %s\n", "app-mode", conf.AppMode()) + fmt.Printf("%-25s %s\n", "app-icon", conf.AppIcon()) + + // URLs. fmt.Printf("%-25s %s\n", "cdn-url", conf.CdnUrl("/")) fmt.Printf("%-25s %s\n", "content-uri", conf.ContentUri()) fmt.Printf("%-25s %s\n", "static-uri", conf.StaticUri()) fmt.Printf("%-25s %s\n", "api-uri", conf.ApiUri()) fmt.Printf("%-25s %s\n", "base-uri", conf.BaseUri("/")) - // HTTP server configuration. + // Web Server.. fmt.Printf("%-25s %s\n", "http-host", conf.HttpHost()) fmt.Printf("%-25s %d\n", "http-port", conf.HttpPort()) fmt.Printf("%-25s %s\n", "http-mode", conf.HttpMode()) - // Database configuration. + // Database. fmt.Printf("%-25s %s\n", "database-driver", dbDriver) fmt.Printf("%-25s %s\n", "database-server", conf.DatabaseServer()) fmt.Printf("%-25s %s\n", "database-host", conf.DatabaseHost()) @@ -113,7 +120,7 @@ func configAction(ctx *cli.Context) error { fmt.Printf("%-25s %d\n", "database-conns", conf.DatabaseConns()) fmt.Printf("%-25s %d\n", "database-conns-idle", conf.DatabaseConnsIdle()) - // External binaries and sidecar configuration. + // External Tools. fmt.Printf("%-25s %t\n", "raw-presets", conf.RawPresets()) fmt.Printf("%-25s %s\n", "darktable-bin", conf.DarktableBin()) fmt.Printf("%-25s %s\n", "darktable-blacklist", conf.DarktableBlacklist()) @@ -127,7 +134,7 @@ func configAction(ctx *cli.Context) error { fmt.Printf("%-25s %d\n", "ffmpeg-buffers", conf.FFmpegBuffers()) fmt.Printf("%-25s %s\n", "exiftool-bin", conf.ExifToolBin()) - // Thumbs, resampling and download security token. + // Thumbnails. fmt.Printf("%-25s %s\n", "download-token", conf.DownloadToken()) fmt.Printf("%-25s %s\n", "preview-token", conf.PreviewToken()) fmt.Printf("%-25s %s\n", "thumb-filter", conf.ThumbFilter()) @@ -138,7 +145,7 @@ func configAction(ctx *cli.Context) error { fmt.Printf("%-25s %d\n", "jpeg-size", conf.JpegSize()) fmt.Printf("%-25s %d\n", "jpeg-quality", conf.JpegQuality()) - // Facial recognition. + // Facial Recognition. fmt.Printf("%-25s %d\n", "face-size", conf.FaceSize()) fmt.Printf("%-25s %f\n", "face-score", conf.FaceScore()) fmt.Printf("%-25s %d\n", "face-overlap", conf.FaceOverlap()) diff --git a/internal/config/client.go b/internal/config/client.go index 3d277ed3d..506709dc3 100644 --- a/internal/config/client.go +++ b/internal/config/client.go @@ -24,11 +24,14 @@ type ClientConfig struct { ApiUri string `json:"apiUri"` ContentUri string `json:"contentUri"` SiteUrl string `json:"siteUrl"` - SitePreview string `json:"sitePreview"` + SiteAuthor string `json:"siteAuthor"` SiteTitle string `json:"siteTitle"` SiteCaption string `json:"siteCaption"` SiteDescription string `json:"siteDescription"` - SiteAuthor string `json:"siteAuthor"` + SitePreview string `json:"sitePreview"` + AppName string `json:"appName"` + AppMode string `json:"appMode"` + AppIcon string `json:"appIcon"` Debug bool `json:"debug"` Test bool `json:"test"` Demo bool `json:"demo"` @@ -194,11 +197,14 @@ func (c *Config) PublicConfig() ClientConfig { ApiUri: c.ApiUri(), ContentUri: c.ContentUri(), SiteUrl: c.SiteUrl(), - SitePreview: c.SitePreview(), + SiteAuthor: c.SiteAuthor(), SiteTitle: c.SiteTitle(), SiteCaption: c.SiteCaption(), SiteDescription: c.SiteDescription(), - SiteAuthor: c.SiteAuthor(), + SitePreview: c.SitePreview(), + AppName: c.AppName(), + AppMode: c.AppMode(), + AppIcon: c.AppIcon(), Version: c.Version(), Copyright: c.Copyright(), Debug: c.Debug(), @@ -257,11 +263,14 @@ func (c *Config) GuestConfig() ClientConfig { ApiUri: c.ApiUri(), ContentUri: c.ContentUri(), SiteUrl: c.SiteUrl(), - SitePreview: c.SitePreview(), + SiteAuthor: c.SiteAuthor(), SiteTitle: c.SiteTitle(), SiteCaption: c.SiteCaption(), SiteDescription: c.SiteDescription(), - SiteAuthor: c.SiteAuthor(), + SitePreview: c.SitePreview(), + AppName: c.AppName(), + AppMode: c.AppMode(), + AppIcon: c.AppIcon(), Version: c.Version(), Copyright: c.Copyright(), Debug: c.Debug(), @@ -314,11 +323,14 @@ func (c *Config) UserConfig() ClientConfig { ApiUri: c.ApiUri(), ContentUri: c.ContentUri(), SiteUrl: c.SiteUrl(), - SitePreview: c.SitePreview(), + SiteAuthor: c.SiteAuthor(), SiteTitle: c.SiteTitle(), SiteCaption: c.SiteCaption(), SiteDescription: c.SiteDescription(), - SiteAuthor: c.SiteAuthor(), + SitePreview: c.SitePreview(), + AppName: c.AppName(), + AppMode: c.AppMode(), + AppIcon: c.AppIcon(), Version: c.Version(), Copyright: c.Copyright(), Debug: c.Debug(), diff --git a/internal/config/config.go b/internal/config/config.go index e7faa8291..26aeecc4f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -329,19 +329,6 @@ func (c *Config) SiteUrl() string { return strings.TrimRight(c.options.SiteUrl, "/") + "/" } -// SitePreview returns the site preview image URL for sharing. -func (c *Config) SitePreview() string { - if c.options.SitePreview == "" { - return c.SiteUrl() + "static/img/preview.jpg" - } - - if !strings.HasPrefix(c.options.SitePreview, "http") { - return c.SiteUrl() + c.options.SitePreview - } - - return c.options.SitePreview -} - // SiteAuthor returns the site author / copyright. func (c *Config) SiteAuthor() string { return c.options.SiteAuthor @@ -370,6 +357,62 @@ func (c *Config) SiteDescription() string { return c.options.SiteDescription } +// SitePreview returns the site preview image URL for sharing. +func (c *Config) SitePreview() string { + if c.options.SitePreview == "" { + return c.SiteUrl() + "static/img/preview.jpg" + } + + if !strings.HasPrefix(c.options.SitePreview, "http") { + return c.SiteUrl() + c.options.SitePreview + } + + return c.options.SitePreview +} + +// AppName returns the app name when installed on a device. +func (c *Config) AppName() string { + name := strings.TrimSpace(c.options.AppName) + + if name == "" { + name = c.SiteTitle() + } + + clean := func(r rune) rune { + switch r { + case '\'', '"': + return -1 + } + + return r + } + + name = strings.Map(clean, name) + + return txt.Clip(name, 32) +} + +// AppMode returns the app mode when installed on a device. +func (c *Config) AppMode() string { + switch c.options.AppMode { + case "fullscreen", "standalone", "minimal-ui", "browser": + return c.options.AppMode + default: + return "standalone" + } +} + +// AppIcon returns the app icon when installed on a device. +func (c *Config) AppIcon() string { + if c.options.AppIcon == "" || c.options.AppIcon == "favicon" { + // Default. + } else if fs.FileExists(filepath.Join(c.ImgPath(), c.options.AppIcon+"-192.png")) { + return c.options.AppIcon + } + + return "favicon" +} + // Debug tests if debug mode is enabled. func (c *Config) Debug() bool { return c.options.Debug diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 24232d69b..893339f36 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -188,6 +188,24 @@ func TestConfig_AdminPassword(t *testing.T) { assert.Equal(t, "photoprism", result) } +func TestConfig_AppName(t *testing.T) { + c := NewConfig(CliTestContext()) + + assert.Equal(t, "config.test", c.AppName()) +} + +func TestConfig_AppMode(t *testing.T) { + c := NewConfig(CliTestContext()) + + assert.Equal(t, "standalone", c.AppMode()) +} + +func TestConfig_ApIcon(t *testing.T) { + c := NewConfig(CliTestContext()) + + assert.Equal(t, "favicon", c.AppIcon()) +} + func TestConfig_NSFWModelPath(t *testing.T) { c := NewConfig(CliTestContext()) diff --git a/internal/config/flags.go b/internal/config/flags.go index ab43d8411..d8c5d050c 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -267,6 +267,24 @@ var GlobalFlags = []cli.Flag{ Usage: "optional preview image `URL`", EnvVar: "PHOTOPRISM_SITE_PREVIEW", }, + cli.StringFlag{ + Name: "app-name", + Usage: "application `NAME` when installed on a device", + Value: "PhotoPrism", + EnvVar: "PHOTOPRISM_APP_NAME", + }, + cli.StringFlag{ + Name: "app-mode", + Usage: "application `MODE` (fullscreen, standalone, minimal-ui, browser)", + Value: "standalone", + EnvVar: "PHOTOPRISM_APP_MODE", + }, + cli.StringFlag{ + Name: "app-icon", + Usage: "application `ICON` (lens, favicon)", + Value: "lens", + EnvVar: "PHOTOPRISM_APP_ICON", + }, cli.IntFlag{ Name: "http-port", Value: 2342, diff --git a/internal/config/options.go b/internal/config/options.go index 7a8449be3..68ef7d671 100644 --- a/internal/config/options.go +++ b/internal/config/options.go @@ -76,11 +76,14 @@ type Options struct { UploadNSFW bool `yaml:"UploadNSFW" json:"-" flag:"upload-nsfw"` CdnUrl string `yaml:"CdnUrl" json:"CdnUrl" flag:"cdn-url"` SiteUrl string `yaml:"SiteUrl" json:"SiteUrl" flag:"site-url"` - SitePreview string `yaml:"SitePreview" json:"SitePreview" flag:"site-preview"` SiteAuthor string `yaml:"SiteAuthor" json:"SiteAuthor" flag:"site-author"` SiteTitle string `yaml:"SiteTitle" json:"SiteTitle" flag:"site-title"` SiteCaption string `yaml:"SiteCaption" json:"SiteCaption" flag:"site-caption"` SiteDescription string `yaml:"SiteDescription" json:"SiteDescription" flag:"site-description"` + SitePreview string `yaml:"SitePreview" json:"SitePreview" flag:"site-preview"` + AppName string `yaml:"AppName" json:"AppName" flag:"app-name"` + AppMode string `yaml:"AppMode" json:"AppMode" flag:"app-mode"` + AppIcon string `yaml:"AppIcon" json:"AppIcon" flag:"app-icon"` DatabaseDriver string `yaml:"DatabaseDriver" json:"-" flag:"database-driver"` DatabaseDsn string `yaml:"DatabaseDsn" json:"-" flag:"database-dsn"` DatabaseServer string `yaml:"DatabaseServer" json:"-" flag:"database-server"`