Frontend: Use canonical key for localStorage

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2025-03-19 11:46:15 +01:00
parent eee392cf08
commit 5fa02a723a
14 changed files with 120 additions and 78 deletions

View File

@@ -15,19 +15,19 @@
{{template "app.gohtml" .}}
<script>{{ if eq .status "success" }}
window.localStorage.setItem("sessionId", {{ .session_id }});
window.localStorage.setItem("authToken", {{ .access_token }});
window.localStorage.setItem("provider", {{ .provider }});
window.localStorage.setItem("user", JSON.stringify({{ .user }}));
window.localStorage.removeItem("authError");
window.localStorage.setItem("session.id", {{ .session_id }});
window.localStorage.setItem("session.token", {{ .access_token }});
window.localStorage.setItem("session.provider", {{ .provider }});
window.localStorage.setItem("session.user", JSON.stringify({{ .user }}));
window.localStorage.removeItem("session.error");
{{ else if eq .status "failed" }}
window.localStorage.setItem("authError", {{ .error }});
window.localStorage.setItem("session.error", {{ .error }});
{{ else }}
window.localStorage.removeItem("sessionId");
window.localStorage.removeItem("authToken");
window.localStorage.removeItem("provider");
window.localStorage.removeItem("user");
window.localStorage.removeItem("authError");
window.localStorage.removeItem("session.id");
window.localStorage.removeItem("session.token");
window.localStorage.removeItem("session.provider");
window.localStorage.removeItem("session.user");
window.localStorage.removeItem("session.error");
{{ end }}
window.location.href = {{ .config.LoginUri }};
</script>

View File

@@ -163,14 +163,14 @@ $config.update().finally(() => {
// Make scroll-pos-restore compatible with bfcache (required to work in PWA mode on iOS).
window.addEventListener("pagehide", (ev) => {
if (ev.persisted) {
localStorage.setItem("lastScrollPosBeforePageHide", JSON.stringify({ x: window.scrollX, y: window.scrollY }));
localStorage.setItem("window.scroll.pos", JSON.stringify({ x: window.scrollX, y: window.scrollY }));
}
});
window.addEventListener("pageshow", (ev) => {
if (ev.persisted) {
const lastSavedScrollPos = localStorage.getItem("lastScrollPosBeforePageHide");
const lastSavedScrollPos = localStorage.getItem("window.scroll.pos");
if (lastSavedScrollPos !== undefined && lastSavedScrollPos !== null && lastSavedScrollPos !== "") {
window.positionToRestore = JSON.parse(localStorage.getItem("lastScrollPosBeforePageHide"));
window.positionToRestore = JSON.parse(localStorage.getItem("window.scroll.pos"));
// Wait for other things that set the scroll-pos anywhere in the app to fire.
setTimeout(() => {
if (window.positionToRestore !== undefined) {
@@ -186,7 +186,7 @@ $config.update().finally(() => {
}
}
localStorage.removeItem("lastScrollPosBeforePageHide");
localStorage.removeItem("window.scroll.pos");
});
// Configure client-side routing.

View File

@@ -47,7 +47,7 @@ const $api = Axios.create({
baseURL: c.apiUri,
headers: {
common: {
"X-Auth-Token": window.localStorage.getItem("authToken"),
"X-Auth-Token": window.localStorage.getItem("session.token"),
"X-Client-Uri": c.jsUri,
"X-Client-Version": c.version,
},

View File

@@ -259,4 +259,4 @@ export class Clipboard {
}
}
export const PhotoClipboard = reactive(new Clipboard(window.localStorage, "photo_clipboard"));
export const PhotoClipboard = reactive(new Clipboard(window.localStorage, "clipboard.photos"));

View File

@@ -41,7 +41,7 @@ export default class Session {
* @param {object} shared
*/
constructor(storage, config, shared) {
this.storageKey = "sessionStorage";
this.storageKey = "session";
this.loginRedirect = false;
this.config = config;
this.provider = "";
@@ -56,18 +56,21 @@ export default class Session {
}
// Restore authentication from session storage.
if (this.applyAuthToken(this.storage.getItem("authToken")) && this.applyId(this.storage.getItem("sessionId"))) {
const dataJson = this.storage.getItem("sessionData");
if (
this.applyAuthToken(this.storage.getItem(this.storageKey + ".token")) &&
this.applyId(this.storage.getItem(this.storageKey + ".id"))
) {
const dataJson = this.storage.getItem(this.storageKey + ".data");
if (dataJson !== "undefined") {
this.data = JSON.parse(dataJson);
}
const userJson = this.storage.getItem("user");
const userJson = this.storage.getItem(this.storageKey + ".user");
if (userJson !== "undefined") {
this.user = new User(JSON.parse(userJson));
}
const provider = this.storage.getItem("provider");
const provider = this.storage.getItem(this.storageKey + ".provider");
if (provider !== null) {
this.provider = provider;
}
@@ -123,7 +126,7 @@ export default class Session {
setAuthToken(authToken) {
if (authToken) {
this.storage.setItem("authToken", authToken);
this.storage.setItem(this.storageKey + ".token", authToken);
if (authToken === PublicAuthToken) {
this.setId(PublicSessionID);
}
@@ -154,7 +157,7 @@ export default class Session {
}
setId(id) {
this.storage.setItem("sessionId", id);
this.storage.setItem(this.storageKey + ".id", id);
this.id = id;
}
@@ -185,20 +188,20 @@ export default class Session {
this.authToken = null;
this.provider = "";
// "sessionId" is the SHA256 hash of the auth token.
this.storage.removeItem("sessionId");
this.storage.removeItem("authToken");
this.storage.removeItem("provider");
// "session.id" is the SHA256 hash of the auth token.
this.storage.removeItem(this.storageKey + ".id");
this.storage.removeItem(this.storageKey + ".token");
this.storage.removeItem(this.storageKey + ".provider");
// The "session_id" storage key is deprecated in favor of "authToken",
// The "session_id" storage key is deprecated in favor of "session.token",
// but should continue to be removed when logging out:
this.storage.removeItem("session_id");
this.storage.removeItem(this.storageKey + ".id");
delete $api.defaults.headers.common[RequestHeader];
}
setProvider(provider) {
this.storage.setItem("provider", provider);
this.storage.setItem(this.storageKey + ".provider", provider);
this.provider = provider;
}
@@ -264,7 +267,7 @@ export default class Session {
}
this.data = data;
this.storage.setItem("sessionData", JSON.stringify(data));
this.storage.setItem(this.storageKey + ".data", JSON.stringify(data));
if (data.user) {
this.setUser(data.user);
@@ -293,7 +296,7 @@ export default class Session {
}
this.user = new User(user);
this.storage.setItem("user", JSON.stringify(user));
this.storage.setItem(this.storageKey + ".user", JSON.stringify(user));
this.auth = this.isUser();
}
@@ -378,7 +381,7 @@ export default class Session {
deleteData() {
this.data = null;
this.storage.removeItem("sessionData");
this.storage.removeItem(this.storageKey + ".data");
}
deleteUser() {
@@ -389,8 +392,8 @@ export default class Session {
deleteClipboard() {
this.storage.removeItem("clipboard");
this.storage.removeItem("photo_clipboard");
this.storage.removeItem("album_clipboard");
this.storage.removeItem("clipboard.photos");
this.storage.removeItem("clipboard.albums");
}
reset() {

View File

@@ -999,7 +999,7 @@ export default {
featFiles: this.$config.feature("files"),
featUsage: canManagePhotos && this.$config.feature("files") && this.$config.values?.usage?.filesTotal,
isRestricted: isRestricted,
isMini: localStorage.getItem("last_navigation_mode") !== "false" || isRestricted,
isMini: localStorage.getItem("navigation.mode") !== "false" || isRestricted,
isDemo: isDemo,
isPro: isPro,
isPublic: isPublic,
@@ -1106,7 +1106,7 @@ export default {
}
this.isMini = !this.isMini;
localStorage.setItem("last_navigation_mode", `${this.isMini}`);
localStorage.setItem("navigation.mode", `${this.isMini}`);
},
showAccountSettings() {
if (this.$config.feature("account")) {

View File

@@ -250,11 +250,11 @@ export default {
},
getViewType() {
let queryParam = this.$route.query["view"] ? this.$route.query["view"] : "";
let defaultType = window.localStorage.getItem("photos_view");
let storedType = window.localStorage.getItem("album_photos_view");
let defaultType = window.localStorage.getItem("photos.view");
let storedType = window.localStorage.getItem("album.photos.view");
if (queryParam) {
window.localStorage.setItem("album_photos_view", queryParam);
window.localStorage.setItem("album.photos.view", queryParam);
return queryParam;
} else if (storedType) {
return storedType;
@@ -422,7 +422,7 @@ export default {
this.settings[key] = value;
}
window.localStorage.setItem("album_photos_" + key, this.settings[key]);
window.localStorage.setItem("album.photos." + key, this.settings[key]);
}
},
updateFilter(props) {

View File

@@ -593,7 +593,7 @@ export default {
},
sortOrder() {
const typeName = this.staticFilter?.type;
const keyName = "albums_order_" + typeName;
const keyName = "albums.order." + typeName;
const queryParam = this.$route.query["order"];
const storedType = window.localStorage.getItem(keyName);
@@ -607,7 +607,7 @@ export default {
return this.defaultOrder;
},
searchCount() {
const offset = parseInt(window.localStorage.getItem("albums_offset"));
const offset = parseInt(window.localStorage.getItem("albums.offset"));
if (this.offset > 0 || !offset) {
return this.batchSize;
@@ -617,7 +617,7 @@ export default {
},
setOffset(offset) {
this.offset = offset;
window.localStorage.setItem("albums_offset", offset);
window.localStorage.setItem("albums.offset", offset);
},
share(album) {
if (!album || !this.canShare) {
@@ -843,7 +843,7 @@ export default {
this.settings[key] = value;
}
window.localStorage.setItem("albums_" + key, this.settings[key]);
window.localStorage.setItem("albums." + key, this.settings[key]);
}
},
updateFilter(props) {

View File

@@ -229,10 +229,10 @@ export default {
},
},
created() {
const authError = window.localStorage.getItem("authError");
const authError = window.localStorage.getItem("session.error");
if (authError) {
this.$notify.error(authError);
window.localStorage.removeItem("authError");
window.localStorage.removeItem("session.error");
}
},
mounted() {

View File

@@ -329,7 +329,7 @@ export default {
this.dialog.edit = true;
},
searchCount() {
const offset = parseInt(window.localStorage.getItem("labels_offset"));
const offset = parseInt(window.localStorage.getItem("labels.offset"));
if (this.offset > 0 || !offset) {
return this.batchSize;
@@ -339,7 +339,7 @@ export default {
},
setOffset(offset) {
this.offset = offset;
window.localStorage.setItem("labels_offset", offset);
window.localStorage.setItem("labels.offset", offset);
},
toggleLike(ev, index) {
if (!this.canManage) {
@@ -570,7 +570,7 @@ export default {
this.settings[key] = value;
}
window.localStorage.setItem("labels_" + key, this.settings[key]);
window.localStorage.setItem("labels." + key, this.settings[key]);
}
},
updateFilter(props) {

View File

@@ -349,7 +349,7 @@ export default {
});
},
searchCount() {
const offset = parseInt(window.localStorage.getItem("subjects_offset"));
const offset = parseInt(window.localStorage.getItem("people.recognized.offset"));
if (this.offset > 0 || !offset) {
return this.batchSize;
@@ -362,7 +362,7 @@ export default {
},
setOffset(offset) {
this.offset = offset;
window.localStorage.setItem("subjects_offset", offset);
window.localStorage.setItem("people.recognized.offset", offset);
},
toggleLike(ev, index) {
if (!this.canManage) {
@@ -606,7 +606,7 @@ export default {
this.settings[key] = value;
}
window.localStorage.setItem("people_" + key, this.settings[key]);
window.localStorage.setItem("people.recognized." + key, this.settings[key]);
}
},
updateFilter(props) {

View File

@@ -302,7 +302,7 @@ export default {
return this.$refs?.toolbar?.hideExpansionPanel();
},
searchCount() {
const offset = parseInt(window.localStorage.getItem("photos_offset"));
const offset = parseInt(window.localStorage.getItem("photos.offset"));
if (this.offset > 0 || !offset) {
return this.batchSize;
}
@@ -310,7 +310,7 @@ export default {
},
setOffset(offset) {
this.offset = offset;
window.localStorage.setItem("photos_offset", offset);
window.localStorage.setItem("photos.offset", offset);
},
getViewType() {
if (this.embedded) {
@@ -318,10 +318,10 @@ export default {
}
let queryParam = this.$route.query["view"] ? this.$route.query["view"] : "";
let storedType = window.localStorage.getItem("photos_view");
let storedType = window.localStorage.getItem("photos.view");
if (queryParam) {
window.localStorage.setItem("photos_view", queryParam);
window.localStorage.setItem("photos.view", queryParam);
return queryParam;
} else if (storedType) {
return storedType;
@@ -358,23 +358,23 @@ export default {
switch (this.getContext()) {
case "archive":
storageKey = "archive_order";
storageKey = "archive.order";
defaultOrder = "archived";
break;
case "favorites":
storageKey = "favorites_order";
storageKey = "favorites.order";
defaultOrder = "newest";
break;
case "hidden":
storageKey = "hidden_order";
storageKey = "hidden.order";
defaultOrder = "added";
break;
case "review":
storageKey = "review_order";
storageKey = "review.order";
defaultOrder = "added";
break;
default:
storageKey = "photos_order";
storageKey = "photos.order";
defaultOrder = "newest";
}
@@ -546,7 +546,7 @@ export default {
this.settings[key] = value;
}
window.localStorage.setItem("photos_" + key, this.settings[key]);
window.localStorage.setItem("photos." + key, this.settings[key]);
}
},
updateFilter(props) {

View File

@@ -69,6 +69,10 @@ import Thumb from "model/thumb";
import PPagePhotos from "page/photos.vue";
import MapStyleControl from "component/places/style-control";
const ProjectionGlobe = "globe";
const ProjectionMercator = "mercator";
const ProjectionVertical = "vertical-perspective";
// Pixels the map pans when the up or down arrow is clicked:
const deltaDistance = 100;
@@ -141,6 +145,7 @@ export default {
config: this.$config.values,
settings: settings.maps,
animate: settings.maps.animate,
skyRendered: false,
};
},
watch: {
@@ -164,7 +169,6 @@ export default {
.then(() => {
this.renderMap();
this.openClusterFromUrl();
this.renderSky();
})
.catch((err) => {
this.mapError = err;
@@ -179,9 +183,10 @@ export default {
},
methods: {
renderSky() {
if (sky.render && this.$refs.background) {
if (!this.skyRendered && sky.render && this.$refs.background) {
this.$nextTick(() => {
sky.render(this.$refs.background, 320);
this.skyRendered = true;
});
}
},
@@ -244,13 +249,27 @@ export default {
const currentProjection = this.map.getProjection()?.type;
let newProjection;
if (currentProjection === "mercator" || !currentProjection) {
newProjection = "globe";
this.map.setZoom(3);
if (currentProjection === ProjectionMercator || !currentProjection) {
this.setProjection(ProjectionGlobe);
} else {
newProjection = "mercator";
this.setProjection(ProjectionMercator);
}
},
setProjection(newProjection) {
const currentProjection = this.map.getProjection()?.type;
if (currentProjection === newProjection) {
return;
}
switch (newProjection) {
case ProjectionGlobe:
this.map.setZoom(3);
break;
case ProjectionMercator:
break;
case ProjectionVertical:
break;
}
this.map.setProjection({ type: newProjection });
@@ -263,7 +282,7 @@ export default {
if (btn && btn instanceof HTMLElement) {
switch (newProjection) {
case "globe":
case ProjectionGlobe:
btn.classList.add("maplibregl-ctrl-globe-enabled");
btn.classList.remove("maplibregl-ctrl-globe");
btn.classList.title = this.map._getUIString("GlobeControl.Disable");
@@ -729,6 +748,7 @@ export default {
.get("geo", options)
.then((response) => {
if (!response.data.features || response.data.features.length === 0) {
this.initialized = true;
this.loading = false;
this.$notify.warn(this.$gettext("No pictures found"));
@@ -756,6 +776,7 @@ export default {
this.updateMarkers();
})
.catch(() => {
this.initialized = true;
this.loading = false;
});
},
@@ -817,6 +838,15 @@ export default {
this.map.on("load", () => this.onMapLoad());
},
onProjectionChange(ev) {
// Remember last used projection.
localStorage.setItem("places.projection", ev.newProjection);
// Render sky if new project is globe.
if (ev.newProjection === ProjectionGlobe) {
this.renderSky();
}
},
getClusterFeatures(clusterId, limit, callback) {
this.map
.getSource("photos")
@@ -1019,7 +1049,16 @@ export default {
this.map.on("idle", this.updateMarkers);
// Load pictures.
this.search();
this.search().finally(() => {
// Call this.onProjectionChange when the projection type changes.
this.map.on("projectiontransition", (ev) => this.onProjectionChange(ev));
// Restore globe projection if last used.
const projection = localStorage.getItem("places.projection");
if (projection === ProjectionGlobe) {
this.setProjection(projection);
}
});
},
},
};

View File

@@ -245,18 +245,18 @@ describe("common/session", () => {
it("should use session storage", () => {
const storage = new StorageShim();
const session = new Session(storage, $config);
assert.equal(storage.getItem("sessionStorage"), null);
assert.equal(storage.getItem("session"), null);
session.useSessionStorage();
assert.equal(storage.getItem("sessionStorage"), "true");
assert.equal(storage.getItem("session"), "true");
session.deleteData();
});
it("should use local storage", () => {
const storage = new StorageShim();
const session = new Session(storage, $config);
assert.equal(storage.getItem("sessionStorage"), null);
assert.equal(storage.getItem("session"), null);
session.useLocalStorage();
assert.equal(storage.getItem("sessionStorage"), "false");
assert.equal(storage.getItem("session"), "false");
session.deleteData();
});