Settings: Add "delete" feature flag and share page title #167
This commit is contained in:
parent
283748cace
commit
801dc49dd7
|
@ -79,8 +79,8 @@ Vue.prototype.$isMobile = isMobile;
|
|||
// Register Vuetify
|
||||
Vue.use(Vuetify, {"theme": config.theme});
|
||||
|
||||
Vue.config.language = config.values.settings.language;
|
||||
Settings.defaultLocale = config.values.settings.language;
|
||||
Vue.config.language = config.values.settings.ui.language;
|
||||
Settings.defaultLocale = Vue.config.language;
|
||||
|
||||
// Register other VueJS plugins
|
||||
Vue.use(GetTextPlugin, {
|
||||
|
|
|
@ -69,7 +69,7 @@ export default class Config {
|
|||
Event.subscribe("count", (ev, data) => this.onCount(ev, data));
|
||||
|
||||
if (this.has("settings")) {
|
||||
this.setTheme(this.get("settings").theme);
|
||||
this.setTheme(this.get("settings").ui.theme);
|
||||
} else {
|
||||
this.setTheme("default");
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ export default class Config {
|
|||
}
|
||||
|
||||
if (values.settings) {
|
||||
this.setTheme(values.settings.theme);
|
||||
this.setTheme(values.settings.ui.theme);
|
||||
}
|
||||
|
||||
return this;
|
||||
|
|
|
@ -22,6 +22,11 @@ body.readonly #photoprism .feature-readonly {
|
|||
display: block !important;
|
||||
}
|
||||
|
||||
body.hide-scrollbar::-webkit-scrollbar {
|
||||
width: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Opacity */
|
||||
|
||||
#photoprism .opacity-0 {
|
||||
|
|
|
@ -32,8 +32,12 @@ import Api from "common/api";
|
|||
import Model from "./model";
|
||||
|
||||
export class Settings extends Model {
|
||||
changed(key) {
|
||||
return (this[key] !== this.__originalValues[key]);
|
||||
changed(area, key) {
|
||||
if (typeof this.__originalValues[area] === "undefined") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (this[area][key] !== this.__originalValues[area][key]);
|
||||
}
|
||||
|
||||
load() {
|
||||
|
|
|
@ -135,7 +135,7 @@
|
|||
:label="$gettext('Theme')"
|
||||
color="secondary-dark"
|
||||
background-color="secondary-light"
|
||||
v-model="settings.theme"
|
||||
v-model="settings.ui.theme"
|
||||
hide-details box
|
||||
class="input-theme"
|
||||
></v-select>
|
||||
|
@ -149,7 +149,7 @@
|
|||
:label="$gettext('Language')"
|
||||
color="secondary-dark"
|
||||
background-color="secondary-light"
|
||||
v-model="settings.language"
|
||||
v-model="settings.ui.language"
|
||||
hide-details box
|
||||
class="input-language"
|
||||
></v-select>
|
||||
|
@ -409,7 +409,7 @@
|
|||
this.settings.load();
|
||||
},
|
||||
onChange() {
|
||||
const reload = this.settings.changed("language");
|
||||
const reload = this.settings.changed("ui", "language");
|
||||
|
||||
if (reload) {
|
||||
this.busy = true;
|
||||
|
|
|
@ -79,8 +79,8 @@ Vue.prototype.$isMobile = isMobile;
|
|||
// Register Vuetify
|
||||
Vue.use(Vuetify, {"theme": config.theme});
|
||||
|
||||
Vue.config.language = config.values.settings.language;
|
||||
Settings.defaultLocale = config.values.settings.language;
|
||||
Vue.config.language = config.values.settings.ui.language;
|
||||
Settings.defaultLocale = Vue.config.language;
|
||||
|
||||
// Register other VueJS plugins
|
||||
Vue.use(GetTextPlugin, {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import Albums from "share/albums.vue";
|
||||
import AlbumPhotos from "share/photos.vue";
|
||||
import {$gettext} from "common/vm";
|
||||
|
||||
const c = window.__CONFIG__;
|
||||
const shareTitle = c.settings.share.title ? c.settings.share.title : c.siteAuthor ? c.siteAuthor : c.name;
|
||||
|
||||
export default [
|
||||
{
|
||||
|
@ -14,14 +14,14 @@ export default [
|
|||
name: "albums",
|
||||
path: "/s/:token",
|
||||
component: Albums,
|
||||
meta: {title: c.siteAuthor, auth: true},
|
||||
meta: {title: shareTitle, auth: true},
|
||||
props: {view: "album", staticFilter: {type: "album"}},
|
||||
},
|
||||
{
|
||||
name: "album",
|
||||
path: "/s/:token/:uid",
|
||||
component: AlbumPhotos,
|
||||
meta: {title: c.siteAuthor, auth: true},
|
||||
meta: {title: shareTitle, auth: true},
|
||||
},
|
||||
{
|
||||
path: "*", redirect: {name: "albums"},
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -41,7 +41,7 @@ describe("model/album", () => {
|
|||
const values = {id: 5, Title: "Christmas 2019", Slug: "christmas-2019", UID: 66};
|
||||
const album = new Album(values);
|
||||
const result = album.thumbnailUrl("xyz");
|
||||
assert.equal(result, "/api/v1/albums/66/t/static/xyz");
|
||||
assert.equal(result, "/api/v1/albums/66/t/public/xyz");
|
||||
});
|
||||
|
||||
it("should get created date string", () => {
|
||||
|
|
|
@ -59,7 +59,7 @@ describe("model/file", () => {
|
|||
Type: "jpg",
|
||||
Name: "1/2/IMG123.jpg"};
|
||||
const file = new File(values);
|
||||
assert.equal(file.thumbnailUrl("abc"), "/api/v1/t/54ghtfd/static/abc");
|
||||
assert.equal(file.thumbnailUrl("abc"), "/api/v1/t/54ghtfd/public/abc");
|
||||
const values2 = {
|
||||
InstanceID: 5,
|
||||
UID: "ABC123",
|
||||
|
@ -84,7 +84,7 @@ describe("model/file", () => {
|
|||
Type: "jpg",
|
||||
Name: "1/2/IMG123.jpg"};
|
||||
const file = new File(values);
|
||||
assert.equal(file.getDownloadUrl("abc"), "/api/v1/dl/54ghtfd?t=1uhovi0e");
|
||||
assert.equal(file.getDownloadUrl("abc"), "/api/v1/dl/54ghtfd?t=2lbh9x09");
|
||||
});
|
||||
|
||||
it("should calculate size", () => {
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -14,18 +14,18 @@ mock
|
|||
|
||||
describe("model/settings", () => {
|
||||
|
||||
it("should return if key was changed", () => {
|
||||
const model = new Settings({"language": "de", "download": false});
|
||||
assert.equal(model.changed("download"), false);
|
||||
assert.equal(model.changed("language"), false);
|
||||
it("should return if key was changed", () => {
|
||||
const model = new Settings({"ui": {"language": "de", "scrollbar": false}});
|
||||
assert.equal(model.changed("ui", "scrollbar"), false);
|
||||
assert.equal(model.changed("ui", "language"), false);
|
||||
});
|
||||
|
||||
it("should load settings", (done) => {
|
||||
const model = new Settings();
|
||||
it("should load settings", (done) => {
|
||||
const model = new Settings({"ui": {"language": "de", "scrollbar": false}});
|
||||
model.load().then(
|
||||
(response) => {
|
||||
assert.equal(response.download, true);
|
||||
assert.equal(response.language, "de");
|
||||
assert.equal(response["ui"]["scrollbar"], false);
|
||||
assert.equal(response["ui"]["language"], "de");
|
||||
done();
|
||||
}
|
||||
).catch(
|
||||
|
@ -35,12 +35,12 @@ describe("model/settings", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("should save settings", (done) => {
|
||||
const model = new Settings({"language": "en"});
|
||||
it("should save settings", (done) => {
|
||||
const model = new Settings({"ui": {"language": "de", "scrollbar": false}});
|
||||
model.save().then(
|
||||
(response) => {
|
||||
assert.equal(response.download, true);
|
||||
assert.equal(response.language, "en");
|
||||
assert.equal(response["ui"]["scrollbar"], false);
|
||||
assert.equal(response["ui"]["language"], "de");
|
||||
done();
|
||||
}
|
||||
).catch(
|
||||
|
|
|
@ -206,7 +206,7 @@ describe("model/thumb", () => {
|
|||
assert.equal(result[0].original_w, 500);
|
||||
});
|
||||
|
||||
it("should return downlaload url", () => {
|
||||
it("should return download url", () => {
|
||||
const values = {
|
||||
InstanceID: 5,
|
||||
UID: "ABC123",
|
||||
|
@ -214,7 +214,7 @@ describe("model/thumb", () => {
|
|||
Type: "jpg",
|
||||
Name: "1/2/IMG123.jpg"};
|
||||
const file = new File(values);
|
||||
assert.equal(Thumb.downloadUrl(file), "/api/v1/dl/54ghtfd?t=1uhovi0e");
|
||||
assert.equal(Thumb.downloadUrl(file), "/api/v1/dl/54ghtfd?t=2lbh9x09");
|
||||
const values2 = {
|
||||
InstanceID: 5,
|
||||
UID: "ABC123",
|
||||
|
@ -232,7 +232,7 @@ describe("model/thumb", () => {
|
|||
Type: "jpg",
|
||||
Name: "1/2/IMG123.jpg"};
|
||||
const file = new File(values);
|
||||
assert.equal(Thumb.thumbnailUrl(file, "abc"), "/api/v1/t/54ghtfd/static/abc");
|
||||
assert.equal(Thumb.thumbnailUrl(file, "abc"), "/api/v1/t/54ghtfd/public/abc");
|
||||
const values2 = {
|
||||
InstanceID: 5,
|
||||
UID: "ABC123",
|
||||
|
|
|
@ -13,9 +13,9 @@ func TestGetSettings(t *testing.T) {
|
|||
app, router, _ := NewApiTest()
|
||||
GetSettings(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/settings")
|
||||
val := gjson.Get(r.Body.String(), "theme")
|
||||
val := gjson.Get(r.Body.String(), "ui.theme")
|
||||
assert.NotEmpty(t, val.String())
|
||||
val2 := gjson.Get(r.Body.String(), "language")
|
||||
val2 := gjson.Get(r.Body.String(), "ui.language")
|
||||
assert.NotEmpty(t, val2.String())
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
})
|
||||
|
@ -26,23 +26,23 @@ func TestSaveSettings(t *testing.T) {
|
|||
app, router, _ := NewApiTest()
|
||||
GetSettings(router)
|
||||
r := PerformRequest(app, "GET", "/api/v1/settings")
|
||||
val := gjson.Get(r.Body.String(), "language")
|
||||
val := gjson.Get(r.Body.String(), "ui.language")
|
||||
assert.Equal(t, "en", val.String())
|
||||
assert.Equal(t, http.StatusOK, r.Code)
|
||||
|
||||
SaveSettings(router)
|
||||
r2 := PerformRequestWithBody(app, "POST", "/api/v1/settings", `{"language": "de"}`)
|
||||
r2 := PerformRequestWithBody(app, "POST", "/api/v1/settings", `{"ui":{"language": "de"}}`)
|
||||
assert.Equal(t, http.StatusOK, r2.Code)
|
||||
r4 := PerformRequest(app, "GET", "/api/v1/settings")
|
||||
val2 := gjson.Get(r4.Body.String(), "language")
|
||||
val2 := gjson.Get(r4.Body.String(), "ui.language")
|
||||
assert.Equal(t, "de", val2.String())
|
||||
r3 := PerformRequestWithBody(app, "POST", "/api/v1/settings", `{"language": "en"}`)
|
||||
r3 := PerformRequestWithBody(app, "POST", "/api/v1/settings", `{"ui":{"language": "en"}}`)
|
||||
assert.Equal(t, http.StatusOK, r3.Code)
|
||||
})
|
||||
t.Run("bad request", func(t *testing.T) {
|
||||
app, router, _ := NewApiTest()
|
||||
SaveSettings(router)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/settings", `{"language": 123}`)
|
||||
r := PerformRequestWithBody(app, "POST", "/api/v1/settings", `{"ui":{"language":123}}`)
|
||||
assert.Equal(t, http.StatusBadRequest, r.Code)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -108,6 +108,10 @@ func (c *Config) Flags() (flags []string) {
|
|||
flags = append(flags, "settings")
|
||||
}
|
||||
|
||||
if !c.Settings().UI.Scrollbar {
|
||||
flags = append(flags, "hide-scrollbar")
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
|
@ -121,10 +125,10 @@ func (c *Config) PublicConfig() ClientConfig {
|
|||
|
||||
result := ClientConfig{
|
||||
Settings: Settings{
|
||||
Language: settings.Language,
|
||||
Theme: settings.Theme,
|
||||
UI: settings.UI,
|
||||
Maps: settings.Maps,
|
||||
Features: settings.Features,
|
||||
Share: settings.Share,
|
||||
},
|
||||
Flags: strings.Join(c.Flags(), " "),
|
||||
Name: c.Name(),
|
||||
|
@ -161,10 +165,10 @@ func (c *Config) GuestConfig() ClientConfig {
|
|||
|
||||
result := ClientConfig{
|
||||
Settings: Settings{
|
||||
Language: settings.Language,
|
||||
Theme: settings.Theme,
|
||||
UI: settings.UI,
|
||||
Maps: settings.Maps,
|
||||
Features: settings.Features,
|
||||
Share: settings.Share,
|
||||
},
|
||||
Flags: "readonly public shared",
|
||||
Name: c.Name(),
|
||||
|
|
|
@ -16,7 +16,14 @@ func (c *Config) SettingsHidden() bool {
|
|||
return c.params.SettingsHidden
|
||||
}
|
||||
|
||||
// TemplateSettings represents HTML template settings for the Web UI.
|
||||
// UISettings represents user interface settings.
|
||||
type UISettings struct {
|
||||
Scrollbar bool `json:"scrollbar" yaml:"scrollbar"`
|
||||
Theme string `json:"theme" yaml:"theme"`
|
||||
Language string `json:"language" yaml:"language"`
|
||||
}
|
||||
|
||||
// TemplateSettings represents template settings for the UI and messaging.
|
||||
type TemplateSettings struct {
|
||||
Default string `json:"default" yaml:"default"`
|
||||
}
|
||||
|
@ -31,7 +38,6 @@ type MapsSettings struct {
|
|||
type FeatureSettings struct {
|
||||
Upload bool `json:"upload" yaml:"upload"`
|
||||
Download bool `json:"download" yaml:"download"`
|
||||
Archive bool `json:"archive" yaml:"archive"`
|
||||
Private bool `json:"private" yaml:"private"`
|
||||
Review bool `json:"review" yaml:"review"`
|
||||
Files bool `json:"files" yaml:"files"`
|
||||
|
@ -39,6 +45,8 @@ type FeatureSettings struct {
|
|||
Labels bool `json:"labels" yaml:"labels"`
|
||||
Places bool `json:"places" yaml:"places"`
|
||||
Edit bool `json:"edit" yaml:"edit"`
|
||||
Archive bool `json:"archive" yaml:"archive"`
|
||||
Delete bool `json:"delete" yaml:"delete"`
|
||||
Share bool `json:"share" yaml:"share"`
|
||||
Library bool `json:"library" yaml:"library"`
|
||||
Import bool `json:"import" yaml:"import"`
|
||||
|
@ -65,23 +73,31 @@ type StackSettings struct {
|
|||
Name bool `json:"name" yaml:"name"`
|
||||
}
|
||||
|
||||
// ShareSettings represents photo sharing settings.
|
||||
type ShareSettings struct {
|
||||
Title string `json:"title" yaml:"title"`
|
||||
}
|
||||
|
||||
// Settings represents user settings for Web UI, indexing, and import.
|
||||
type Settings struct {
|
||||
Theme string `json:"theme" yaml:"theme"`
|
||||
Language string `json:"language" yaml:"language"`
|
||||
UI UISettings `json:"ui" yaml:"ui"`
|
||||
Templates TemplateSettings `json:"templates" yaml:"templates"`
|
||||
Maps MapsSettings `json:"maps" yaml:"maps"`
|
||||
Features FeatureSettings `json:"features" yaml:"features"`
|
||||
Import ImportSettings `json:"import" yaml:"import"`
|
||||
Index IndexSettings `json:"index" yaml:"index"`
|
||||
Stack StackSettings `json:"stack" yaml:"stack"`
|
||||
Share ShareSettings `json:"share" yaml:"share"`
|
||||
}
|
||||
|
||||
// NewSettings creates a new Settings instance.
|
||||
func NewSettings() *Settings {
|
||||
return &Settings{
|
||||
Theme: "default",
|
||||
Language: "en",
|
||||
UI: UISettings{
|
||||
Scrollbar: true,
|
||||
Theme: "default",
|
||||
Language: "en",
|
||||
},
|
||||
Templates: TemplateSettings{
|
||||
Default: "index.tmpl",
|
||||
},
|
||||
|
@ -124,7 +140,7 @@ func NewSettings() *Settings {
|
|||
|
||||
// Propagate updates settings in other packages as needed.
|
||||
func (s *Settings) Propagate() {
|
||||
i18n.SetLocale(s.Language)
|
||||
i18n.SetLocale(s.UI.Language)
|
||||
}
|
||||
|
||||
// StackSequences tests if files should be stacked based on their file name prefix (sequential names).
|
||||
|
|
|
@ -17,50 +17,54 @@ func TestSettings_Load(t *testing.T) {
|
|||
t.Run("existing filename", func(t *testing.T) {
|
||||
c := NewSettings()
|
||||
|
||||
if err := c.Load("testdata/config.yml"); err != nil {
|
||||
if err := c.Load("testdata/settings.yml"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "lavendel", c.Theme)
|
||||
assert.Equal(t, "english", c.Language)
|
||||
assert.Equal(t, "onyx", c.UI.Theme)
|
||||
assert.Equal(t, "de", c.UI.Language)
|
||||
})
|
||||
t.Run("not existing filename", func(t *testing.T) {
|
||||
c := NewSettings()
|
||||
|
||||
err := c.Load("testdata/config123.yml")
|
||||
err := c.Load("testdata/settings_123.yml")
|
||||
|
||||
assert.Error(t, err)
|
||||
|
||||
assert.Equal(t, "default", c.Theme)
|
||||
assert.Equal(t, "en", c.Language)
|
||||
assert.Equal(t, "default", c.UI.Theme)
|
||||
assert.Equal(t, "en", c.UI.Language)
|
||||
})
|
||||
}
|
||||
func TestSettings_Save(t *testing.T) {
|
||||
t.Run("existing filename", func(t *testing.T) {
|
||||
c := NewSettings()
|
||||
c.Theme = "lavendel"
|
||||
c.Language = "german"
|
||||
|
||||
assert.Equal(t, "lavendel", c.Theme)
|
||||
assert.Equal(t, "german", c.Language)
|
||||
assert.Equal(t, "default", c.UI.Theme)
|
||||
assert.Equal(t, "en", c.UI.Language)
|
||||
|
||||
if err := c.Save("testdata/configEmpty.yml"); err != nil {
|
||||
c.UI.Theme = "onyx"
|
||||
c.UI.Language = "de"
|
||||
|
||||
assert.Equal(t, "onyx", c.UI.Theme)
|
||||
assert.Equal(t, "de", c.UI.Language)
|
||||
|
||||
if err := c.Save("testdata/settings.yml"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
t.Run("not existing filename", func(t *testing.T) {
|
||||
c := NewSettings()
|
||||
c.Theme = "lavendel"
|
||||
c.Language = "german"
|
||||
c.UI.Theme = "onyx"
|
||||
c.UI.Language = "de"
|
||||
|
||||
assert.Equal(t, "lavendel", c.Theme)
|
||||
assert.Equal(t, "german", c.Language)
|
||||
assert.Equal(t, "onyx", c.UI.Theme)
|
||||
assert.Equal(t, "de", c.UI.Language)
|
||||
|
||||
if err := c.Save("testdata/configEmpty123.yml"); err != nil {
|
||||
if err := c.Save("testdata/settings_tmp.yml"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := os.Remove("testdata/configEmpty123.yml"); err != nil {
|
||||
if err := os.Remove("testdata/settings_tmp.yml"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
|
11
internal/config/testdata/configEmpty.yml → internal/config/testdata/settings.yml
vendored
Normal file → Executable file
11
internal/config/testdata/configEmpty.yml → internal/config/testdata/settings.yml
vendored
Normal file → Executable file
|
@ -1,5 +1,7 @@
|
|||
theme: lavendel
|
||||
language: german
|
||||
ui:
|
||||
scrollbar: true
|
||||
theme: onyx
|
||||
language: de
|
||||
templates:
|
||||
default: index.tmpl
|
||||
maps:
|
||||
|
@ -8,7 +10,6 @@ maps:
|
|||
features:
|
||||
upload: true
|
||||
download: true
|
||||
archive: true
|
||||
private: true
|
||||
review: true
|
||||
files: true
|
||||
|
@ -16,6 +17,8 @@ features:
|
|||
labels: true
|
||||
places: true
|
||||
edit: true
|
||||
archive: true
|
||||
delete: false
|
||||
share: true
|
||||
library: true
|
||||
import: true
|
||||
|
@ -31,3 +34,5 @@ stack:
|
|||
uuid: true
|
||||
meta: true
|
||||
name: false
|
||||
share:
|
||||
title: ""
|
Loading…
Reference in a new issue