diff --git a/frontend/src/app.js b/frontend/src/app.js index 1b446a4f0..f7b9717f4 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -87,7 +87,7 @@ Vue.prototype.$isMobile = isMobile; Vue.prototype.$rtl = rtl; // Register Vuetify -Vue.use(Vuetify, { rtl, theme: config.theme }); +Vue.use(Vuetify, { rtl, theme: config.theme.colors }); // Register other VueJS plugins Vue.use(GetTextPlugin, { diff --git a/frontend/src/common/config.js b/frontend/src/common/config.js index 6f9ea8abd..c4b2843f6 100644 --- a/frontend/src/common/config.js +++ b/frontend/src/common/config.js @@ -192,10 +192,17 @@ export default class Config { setTheme(name) { this.themeName = name; + + const el = document.getElementById("photoprism"); + + if (el) { + el.className = "theme-" + name; + } + this.theme = themes[name] ? themes[name] : themes["default"]; if (this.$vuetify) { - this.$vuetify.theme = this.theme; + this.$vuetify.theme = this.theme.colors; } return this; diff --git a/frontend/src/css/themes.css b/frontend/src/css/themes.css index 2ac8aeb2a..74efc162d 100644 --- a/frontend/src/css/themes.css +++ b/frontend/src/css/themes.css @@ -1,75 +1,74 @@ -.theme-onyx .v-content__wrap, -.theme-onyx .p-page, -.theme-onyx .form, -.theme-onyx .v-content { +/* Dark Theme */ + +.theme-grayscale .v-content__wrap, +.theme-grayscale .p-page, +.theme-grayscale .form, +.theme-grayscale .v-content { background: #525252 !important; } -#photoprism.theme-onyx .theme--light.v-small-dialog__content, -#photoprism.theme-onyx .theme--light.v-sheet, -#photoprism.theme-onyx .theme--light.v-card { +#photoprism.theme-grayscale .theme--light.v-small-dialog__content, +#photoprism.theme-grayscale .theme--light.v-sheet, +#photoprism.theme-grayscale .theme--light.v-card { background: #525252; } -.theme-onyx .application.theme--light { +.theme-grayscale .application.theme--light { background: #525252; } -#photoprism.theme-onyx .theme--light .v-table { +#photoprism.theme-grayscale .theme--light .v-table { background: #4E4E4E; } -#photoprism.theme-onyx .theme--light.v-table thead th, -#photoprism.theme-onyx .theme--light.v-table tbody td { +#photoprism.theme-grayscale .theme--light.v-table thead th, +#photoprism.theme-grayscale .theme--light.v-table tbody td { color: #fff; } -#photoprism.theme-onyx .theme--light.v-table tbody tr:hover { +#photoprism.theme-grayscale .theme--light.v-table tbody tr:hover { background: #666; } -#photoprism.theme-onyx .theme--light.v-chip, -#photoprism.theme-onyx .v-card__actions .theme--light.v-text-field--solo>.v-input__control>.v-input__slot { +#photoprism.theme-grayscale .theme--light.v-chip, +#photoprism.theme-grayscale .v-card__actions .theme--light.v-text-field--solo>.v-input__control>.v-input__slot { background: #777; } -#photoprism.theme-onyx .theme--light.v-text-field--solo>.v-input__control>.v-input__slot { +#photoprism.theme-grayscale .theme--light.v-text-field--solo>.v-input__control>.v-input__slot { background: #4E4E4E; } -#photoprism.theme-onyx, -#photoprism.theme-onyx .p-page a, -#photoprism.theme-onyx .v-datatable a, -#photoprism.theme-onyx .theme--light.v-expansion-panel .v-expansion-panel__container, -#photoprism.theme-onyx .theme--light.v-tabs__bar .v-tabs__div, -#photoprism.theme-onyx .theme--light { +#photoprism.theme-grayscale, +#photoprism.theme-grayscale .p-page a, +#photoprism.theme-grayscale .v-datatable a, +#photoprism.theme-grayscale .theme--light.v-expansion-panel .v-expansion-panel__container, +#photoprism.theme-grayscale .theme--light.v-tabs__bar .v-tabs__div, +#photoprism.theme-grayscale .theme--light { color: #fff; } -#photoprism.theme-onyx .theme--light.v-list { +#photoprism.theme-grayscale .theme--light.v-list { background: #555; } -#photoprism.theme-onyx .theme--light.v-select .v-select__selections { +#photoprism.theme-grayscale .p-page a.text-link, +#photoprism.theme-grayscale .theme--light.v-select .v-select__selections { color: #e3d5c3; } -#photoprism.theme-onyx .theme--light.v-list .v-list__tile__sub-title, -#photoprism.theme-onyx .accent--text { +#photoprism.theme-grayscale .theme--light.v-list .v-list__tile__sub-title, +#photoprism.theme-grayscale .accent--text { color: #e3d5c3 !important; } -#photoprism.theme-onyx .p-page a.text-link { - color: #e3d5c3; -} - -#photoprism.theme-onyx .theme--light.v-input:not(.v-input--is-disabled) input, -#photoprism.theme-onyx .theme--light.v-input:not(.v-input--is-disabled) textarea { +#photoprism.theme-grayscale .theme--light.v-input:not(.v-input--is-disabled) input, +#photoprism.theme-grayscale .theme--light.v-input:not(.v-input--is-disabled) textarea { color: #fff; } -#photoprism.theme-onyx .theme--light.v-btn.v-btn--disabled, -#photoprism.theme-onyx .theme--light.v-btn.v-btn--disabled .v-btn__loading, -#photoprism.theme-onyx .theme--light.v-btn.v-btn--disabled .v-icon { +#photoprism.theme-grayscale .theme--light.v-btn.v-btn--disabled, +#photoprism.theme-grayscale .theme--light.v-btn.v-btn--disabled .v-btn__loading, +#photoprism.theme-grayscale .theme--light.v-btn.v-btn--disabled .v-icon { color: #999 !important; } \ No newline at end of file diff --git a/frontend/src/options/options.js b/frontend/src/options/options.js index 9756c3fe7..af29d8488 100644 --- a/frontend/src/options/options.js +++ b/frontend/src/options/options.js @@ -153,30 +153,42 @@ export const Themes = () => [ { text: $gettext("Default"), value: "default", + disabled: false, + }, + { + text: $gettext("Grayscale"), + value: "grayscale", + disabled: !config.values.sponsor, }, { text: $gettext("Cyano"), value: "cyano", + disabled: false, }, { text: $gettext("Lavender"), value: "lavender", + disabled: false, }, { text: $gettext("Moonlight"), value: "moonlight", + disabled: false, }, { text: $gettext("Onyx"), value: "onyx", + disabled: false, }, { text: $gettext("Raspberry"), value: "raspberry", + disabled: false, }, { text: $gettext("Seaweed"), value: "seaweed", + sdisabled: false, }, ]; export const MapsAnimate = () => [ diff --git a/frontend/src/options/themes.json b/frontend/src/options/themes.json index 2713c59c8..067977ef2 100644 --- a/frontend/src/options/themes.json +++ b/frontend/src/options/themes.json @@ -1,170 +1,226 @@ { "default": { - "application": "#F5F5F5", - "form": "#eeeeee", - "primary": "#FFCA28", - "secondary-dark": "#212121", - "secondary": "#bdbdbd", - "secondary-light": "#e0e0e0", - "accent": "#757575", - "error": "#e57373", - "info": "#00acc1", - "success": "#4db6ac", - "warning": "#ffd740", - "remove": "#e57373", - "restore": "#64b5f6", - "album": "#ffab00", - "download": "#00bfa5", - "private": "#00b8d4", - "edit": "#00b8d4", - "share": "#9575cd", - "love": "#ef5350", - "terminal": "#333333", - "navigation": "#212121", - "navigation-home": "#000000" + "dark": false, + "sponsor": false, + "colors": { + "application": "#F5F5F5", + "form": "#eeeeee", + "primary": "#FFCA28", + "secondary-dark": "#212121", + "secondary": "#bdbdbd", + "secondary-light": "#e0e0e0", + "accent": "#757575", + "error": "#e57373", + "info": "#00acc1", + "success": "#4db6ac", + "warning": "#ffd740", + "remove": "#e57373", + "restore": "#64b5f6", + "album": "#ffab00", + "download": "#00bfa5", + "private": "#00b8d4", + "edit": "#00b8d4", + "share": "#9575cd", + "love": "#ef5350", + "terminal": "#333333", + "navigation": "#212121", + "navigation-home": "#000000" + } + }, + "grayscale": { + "dark": true, + "sponsor": true, + "colors": { + "application": "#525252", + "form": "#e5e4e2", + "primary": "#c8bdb1", + "secondary-dark": "#aba095", + "secondary": "#444", + "secondary-light": "#666", + "accent": "#333", + "error": "#e57373", + "info": "#5a94dd", + "success": "#4db6ac", + "warning": "#e3d181", + "love": "#ef5350", + "remove": "#e35333", + "restore": "#64b5f6", + "album": "#ffab40", + "download": "#07bd9f", + "private": "#48bcd6", + "edit": "#48bcd6", + "share": "#0070a0", + "terminal": "#333333", + "navigation": "#353839", + "navigation-home": "#212121" + } }, "cyano": { - "application": "#eceff1", - "form": "#eceff1", - "primary": "#80deea", - "secondary-dark": "#757575", - "secondary": "#b0bec5", - "secondary-light": "#eeeeee", - "accent": "#757575", - "error": "#e57373", - "info": "#00acc1", - "success": "#4db6ac", - "warning": "#ffd740", - "remove": "#e57373", - "restore": "#64b5f6", - "album": "#ffab00", - "download": "#00bfa5", - "private": "#00b8d4", - "edit": "#00b8d4", - "share": "#9575cd", - "love": "#ef5350", - "terminal": "#333333", - "navigation": "#006064", - "navigation-home": "#37474f" + "dark": false, + "sponsor": false, + "colors": { + "application": "#eceff1", + "form": "#eceff1", + "primary": "#80deea", + "secondary-dark": "#757575", + "secondary": "#b0bec5", + "secondary-light": "#eeeeee", + "accent": "#757575", + "error": "#e57373", + "info": "#00acc1", + "success": "#4db6ac", + "warning": "#ffd740", + "remove": "#e57373", + "restore": "#64b5f6", + "album": "#ffab00", + "download": "#00bfa5", + "private": "#00b8d4", + "edit": "#00b8d4", + "share": "#9575cd", + "love": "#ef5350", + "terminal": "#333333", + "navigation": "#006064", + "navigation-home": "#37474f" + } }, "raspberry": { - "application": "#fafafa", - "form": "#fafafa", - "primary": "#ffb3cc", - "secondary-dark": "#757575", - "secondary": "#fce4ec", - "secondary-light": "#eeeeee", - "accent": "#9e9e9e", - "error": "#e57373", - "info": "#00acc1", - "success": "#4db6ac", - "warning": "#ffd740", - "remove": "#e57373", - "restore": "#64b5f6", - "album": "#ffab00", - "download": "#00bfa5", - "private": "#00b8d4", - "edit": "#00b8d4", - "share": "#9575cd", - "love": "#ef5350", - "terminal": "#616161", - "navigation": "#a40e52", - "navigation-home": "#880e4f" + "dark": false, + "sponsor": false, + "colors": { + "application": "#fafafa", + "form": "#fafafa", + "primary": "#ffb3cc", + "secondary-dark": "#757575", + "secondary": "#fce4ec", + "secondary-light": "#eeeeee", + "accent": "#9e9e9e", + "error": "#e57373", + "info": "#00acc1", + "success": "#4db6ac", + "warning": "#ffd740", + "remove": "#e57373", + "restore": "#64b5f6", + "album": "#ffab00", + "download": "#00bfa5", + "private": "#00b8d4", + "edit": "#00b8d4", + "share": "#9575cd", + "love": "#ef5350", + "terminal": "#616161", + "navigation": "#a40e52", + "navigation-home": "#880e4f" + } }, "seaweed": { - "application": "#fafafa", - "form": "#fafafa", - "primary": "#26a69a", - "secondary-dark": "#26a69a", - "secondary": "#b0bec5", - "secondary-light": "#b0bec5", - "accent": "#b0bec5", - "error": "#e57373", - "info": "#00acc1", - "success": "#4db6ac", - "warning": "#ffd740", - "remove": "#e57373", - "restore": "#64b5f6", - "album": "#ffab00", - "download": "#00bfa5", - "private": "#00b8d4", - "edit": "#00b8d4", - "share": "#9575cd", - "love": "#ef5350", - "terminal": "#333333", - "navigation": "#37474f", - "navigation-home": "#263238" + "dark": false, + "sponsor": false, + "colors": { + "application": "#fafafa", + "form": "#fafafa", + "primary": "#26a69a", + "secondary-dark": "#26a69a", + "secondary": "#b0bec5", + "secondary-light": "#b0bec5", + "accent": "#b0bec5", + "error": "#e57373", + "info": "#00acc1", + "success": "#4db6ac", + "warning": "#ffd740", + "remove": "#e57373", + "restore": "#64b5f6", + "album": "#ffab00", + "download": "#00bfa5", + "private": "#00b8d4", + "edit": "#00b8d4", + "share": "#9575cd", + "love": "#ef5350", + "terminal": "#333333", + "navigation": "#37474f", + "navigation-home": "#263238" + } }, "lavender": { - "application": "#fafafa", - "form": "#fafafa", - "primary": "#8c9eff", - "secondary-dark": "#8c9eff", - "secondary": "#b0bec5", - "secondary-light": "#b0bec5", - "accent": "#b0bec5", - "error": "#e57373", - "info": "#00acc1", - "success": "#4db6ac", - "warning": "#ffd740", - "remove": "#e57373", - "restore": "#64b5f6", - "album": "#ffab00", - "download": "#00bfa5", - "private": "#00b8d4", - "edit": "#00b8d4", - "share": "#9575cd", - "love": "#ef5350", - "terminal": "#333333", - "navigation": "#37474f", - "navigation-home": "#263238" + "dark": false, + "sponsor": false, + "colors": { + "application": "#fafafa", + "form": "#fafafa", + "primary": "#8c9eff", + "secondary-dark": "#8c9eff", + "secondary": "#b0bec5", + "secondary-light": "#b0bec5", + "accent": "#b0bec5", + "error": "#e57373", + "info": "#00acc1", + "success": "#4db6ac", + "warning": "#ffd740", + "remove": "#e57373", + "restore": "#64b5f6", + "album": "#ffab00", + "download": "#00bfa5", + "private": "#00b8d4", + "edit": "#00b8d4", + "share": "#9575cd", + "love": "#ef5350", + "terminal": "#333333", + "navigation": "#37474f", + "navigation-home": "#263238" + } }, "moonlight": { - "application": "#eeeeee", - "form": "#eeeeee", - "primary": "#a5b3c0", - "secondary-dark": "#212121", - "secondary": "#bdbdbd", - "secondary-light": "#e0e0e0", - "accent": "#757575", - "error": "#e57373", - "info": "#00acc1", - "success": "#4db6ac", - "warning": "#ffd740", - "remove": "#e57373", - "restore": "#64b5f6", - "album": "#ffab00", - "download": "#00bfa5", - "private": "#00b8d4", - "edit": "#00b8d4", - "share": "#9575cd", - "love": "#ef5350", - "terminal": "#333333", - "navigation": "#212121", - "navigation-home": "#000000" + "dark": false, + "sponsor": false, + "colors": { + "application": "#eeeeee", + "form": "#eeeeee", + "primary": "#a5b3c0", + "secondary-dark": "#212121", + "secondary": "#bdbdbd", + "secondary-light": "#e0e0e0", + "accent": "#757575", + "error": "#e57373", + "info": "#00acc1", + "success": "#4db6ac", + "warning": "#ffd740", + "remove": "#e57373", + "restore": "#64b5f6", + "album": "#ffab00", + "download": "#00bfa5", + "private": "#00b8d4", + "edit": "#00b8d4", + "share": "#9575cd", + "love": "#ef5350", + "terminal": "#333333", + "navigation": "#212121", + "navigation-home": "#000000" + } }, "onyx": { - "application": "#525252", - "form": "#e5e4e2", - "primary": "#c8bdb1", - "secondary-dark": "#aba095", - "secondary": "#444", - "secondary-light": "#666", - "accent": "#333", - "error": "#e57373", - "info": "#5a94dd", - "success": "#4db6ac", - "warning": "#e3d181", - "love": "#ef5350", - "remove": "#e35333", - "restore": "#64b5f6", - "album": "#ffab40", - "download": "#07bd9f", - "private": "#48bcd6", - "edit": "#48bcd6", - "share": "#0070a0", - "terminal": "#333333", - "navigation": "#353839", - "navigation-home": "#212121" + "dark": false, + "sponsor": false, + "colors": { + "application": "#e5e4e2", + "form": "#e5e4e2", + "primary": "#c8bdb1", + "secondary-dark": "#353839", + "secondary": "#a8a8a8", + "secondary-light": "#cdccca", + "accent": "#656565", + "error": "#e57373", + "info": "#5a94dd", + "success": "#4db6ac", + "warning": "#e3d181", + "love": "#ef5350", + "remove": "#e35333", + "restore": "#64b5f6", + "album": "#ffab40", + "download": "#07bd9f", + "private": "#48bcd6", + "edit": "#48bcd6", + "share": "#0070a0", + "terminal": "#333333", + "navigation": "#353839", + "navigation-home": "#212121" + } } } diff --git a/frontend/src/pages/settings/general.vue b/frontend/src/pages/settings/general.vue index df5ff3fd9..479d684dd 100644 --- a/frontend/src/pages/settings/general.vue +++ b/frontend/src/pages/settings/general.vue @@ -14,8 +14,8 @@ { this.settings.setValues(this.$config.settings()); this.busy = false; - }) + }); }, onChange() { const reload = this.settings.changed("ui", "language"); diff --git a/frontend/src/share.js b/frontend/src/share.js index 3caf0895f..91d92cc30 100644 --- a/frontend/src/share.js +++ b/frontend/src/share.js @@ -86,7 +86,7 @@ Vue.prototype.$isMobile = isMobile; Vue.prototype.$rtl = rtl; // Register Vuetify -Vue.use(Vuetify, { rtl, theme: config.theme }); +Vue.use(Vuetify, { rtl, theme: config.theme.colors }); // Register other VueJS plugins Vue.use(GetTextPlugin, { diff --git a/internal/config/client.go b/internal/config/client.go index df48a2fef..709a10b94 100644 --- a/internal/config/client.go +++ b/internal/config/client.go @@ -25,6 +25,7 @@ type ClientConfig struct { SiteAuthor string `json:"siteAuthor"` Debug bool `json:"debug"` Demo bool `json:"demo"` + Sponsor bool `json:"sponsor"` ReadOnly bool `json:"readonly"` UploadNSFW bool `json:"uploadNSFW"` Public bool `json:"public"` @@ -166,6 +167,7 @@ func (c *Config) PublicConfig() ClientConfig { Copyright: c.Copyright(), Debug: c.Debug(), Demo: c.Demo(), + Sponsor: c.Sponsor(), ReadOnly: c.ReadOnly(), Public: c.Public(), Experimental: c.Experimental(), @@ -216,6 +218,7 @@ func (c *Config) GuestConfig() ClientConfig { Copyright: c.Copyright(), Debug: c.Debug(), Demo: c.Demo(), + Sponsor: c.Sponsor(), ReadOnly: true, UploadNSFW: c.UploadNSFW(), Public: true, @@ -260,6 +263,7 @@ func (c *Config) UserConfig() ClientConfig { Copyright: c.Copyright(), Debug: c.Debug(), Demo: c.Demo(), + Sponsor: c.Sponsor(), ReadOnly: c.ReadOnly(), UploadNSFW: c.UploadNSFW(), Public: c.Public(), diff --git a/internal/config/config.go b/internal/config/config.go index cc5c1973f..246b8b611 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -270,6 +270,11 @@ func (c *Config) Demo() bool { return c.options.Demo } +// Your continuous support helps to pay for development, hosting, and external services like satellite maps. +func (c *Config) Sponsor() bool { + return c.options.Sponsor +} + // Public tests if app runs in public mode and requires no authentication. func (c *Config) Public() bool { if c.Demo() { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index d8ab92b72..cdca60995 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -251,6 +251,7 @@ func TestConfig_ClientConfig(t *testing.T) { assert.NotEmpty(t, cc.ManifestHash) assert.Equal(t, true, cc.Debug) assert.Equal(t, false, cc.Demo) + assert.Equal(t, false, cc.Sponsor) assert.Equal(t, false, cc.ReadOnly) } diff --git a/internal/config/flags.go b/internal/config/flags.go index c7230e96f..31504613b 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -18,6 +18,12 @@ var GlobalFlags = []cli.Flag{ Usage: "run in demo mode", EnvVar: "PHOTOPRISM_DEMO", }, + cli.BoolFlag{ + Name: "sponsor", + Hidden: true, + Usage: "your continuous support helps to pay for development, hosting, and external services like satellite maps", + EnvVar: "PHOTOPRISM_SPONSOR", + }, cli.BoolFlag{ Name: "public, p", Usage: "no authentication required, disables password protection", diff --git a/internal/config/options.go b/internal/config/options.go index 6ec64ec57..5c71567ef 100644 --- a/internal/config/options.go +++ b/internal/config/options.go @@ -35,6 +35,7 @@ type Options struct { Copyright string `json:"-"` Debug bool `yaml:"Debug" json:"Debug" flag:"debug"` Demo bool `yaml:"Demo" json:"-" flag:"demo"` + Sponsor bool `yaml:"-" json:"-" flag:"sponsor"` Public bool `yaml:"Public" json:"-" flag:"public"` ReadOnly bool `yaml:"ReadOnly" json:"ReadOnly" flag:"read-only"` Experimental bool `yaml:"Experimental" json:"Experimental" flag:"experimental"`