mirror of
https://github.com/photoprism/photoprism.git
synced 2026-07-17 16:49:04 +00:00
Frontend: Enforce braced if-bodies via ESLint curly rule #4966
This commit is contained in:
parent
e92c4ee80b
commit
bdd33df25b
30 changed files with 667 additions and 224 deletions
|
|
@ -84,6 +84,7 @@ export default defineConfig([
|
|||
},
|
||||
],
|
||||
"semi": ["error", "always"],
|
||||
"curly": ["warn", "all"],
|
||||
"no-unused-vars": ["warn"],
|
||||
"no-console": 0,
|
||||
"no-case-declarations": 0,
|
||||
|
|
|
|||
|
|
@ -229,7 +229,9 @@ export class Clipboard {
|
|||
}
|
||||
|
||||
setIds(ids) {
|
||||
if (!Array.isArray(ids)) return;
|
||||
if (!Array.isArray(ids)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selection = ids;
|
||||
this.selectionMap = {};
|
||||
|
|
|
|||
|
|
@ -402,7 +402,9 @@ export default class Config {
|
|||
const perms = ["update", "search", "manage", "share", "delete"];
|
||||
|
||||
perms.forEach((perm) => {
|
||||
if (this.deny(resource, perm)) result.push(`disable-${perm}`);
|
||||
if (this.deny(resource, perm)) {
|
||||
result.push(`disable-${perm}`);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ const state = {
|
|||
|
||||
function evict(field) {
|
||||
const slot = state[field];
|
||||
if (!slot) return;
|
||||
if (!slot) {
|
||||
return;
|
||||
}
|
||||
slot.data = null;
|
||||
slot.fetch = null;
|
||||
}
|
||||
|
|
@ -44,8 +46,12 @@ function fetchAlbums() {
|
|||
|
||||
function get(field, fetcher) {
|
||||
const slot = state[field];
|
||||
if (slot.data) return Promise.resolve(slot.data);
|
||||
if (slot.fetch) return slot.fetch;
|
||||
if (slot.data) {
|
||||
return Promise.resolve(slot.data);
|
||||
}
|
||||
if (slot.fetch) {
|
||||
return slot.fetch;
|
||||
}
|
||||
slot.fetch = fetcher()
|
||||
.then((data) => {
|
||||
slot.data = data;
|
||||
|
|
|
|||
|
|
@ -100,7 +100,9 @@ export default class $util {
|
|||
// spaces and trimmed. Emoji sequences (ZWJ, skin tone, regional indicators)
|
||||
// are preserved so emoji-only titles round-trip.
|
||||
static normalizeTitle(s) {
|
||||
if (s === null || s === undefined) return "";
|
||||
if (s === null || s === undefined) {
|
||||
return "";
|
||||
}
|
||||
return (
|
||||
String(s)
|
||||
.toLowerCase()
|
||||
|
|
@ -116,7 +118,9 @@ export default class $util {
|
|||
);
|
||||
}
|
||||
static slugifyLabelTitle(s) {
|
||||
if (s === null || s === undefined) return "";
|
||||
if (s === null || s === undefined) {
|
||||
return "";
|
||||
}
|
||||
return String(s)
|
||||
.toLowerCase()
|
||||
.replace(/&/g, "and")
|
||||
|
|
@ -318,8 +322,9 @@ export default class $util {
|
|||
I: 1,
|
||||
};
|
||||
let a;
|
||||
if (number < 1 || number > 3999) return "";
|
||||
else {
|
||||
if (number < 1 || number > 3999) {
|
||||
return "";
|
||||
} else {
|
||||
for (let key in romanNumList) {
|
||||
a = Math.floor(number / romanNumList[key]);
|
||||
if (a >= 0) {
|
||||
|
|
|
|||
|
|
@ -358,7 +358,9 @@ const InteractiveTargetSelector = 'button, input, textarea, select, a[href], [ro
|
|||
|
||||
// isInteractiveTarget reports whether the touch target is (or sits inside) a tappable widget.
|
||||
function isInteractiveTarget(target) {
|
||||
if (!target || typeof target.closest !== "function") return false;
|
||||
if (!target || typeof target.closest !== "function") {
|
||||
return false;
|
||||
}
|
||||
return target.closest(InteractiveTargetSelector) !== null;
|
||||
}
|
||||
|
||||
|
|
@ -366,15 +368,25 @@ function isInteractiveTarget(target) {
|
|||
// accidental horizontal navigation while the lightbox is active. Scoped to edge bands
|
||||
// only — inner-area touches and taps on interactive widgets pass through.
|
||||
export function preventNavigationTouchEvent(ev) {
|
||||
if (!(ev instanceof TouchEvent) || !ev.cancelable) return;
|
||||
if (ev.type !== TouchStartEvent && ev.type !== TouchMoveEvent) return;
|
||||
if (!(ev instanceof TouchEvent) || !ev.cancelable) {
|
||||
return;
|
||||
}
|
||||
if (ev.type !== TouchStartEvent && ev.type !== TouchMoveEvent) {
|
||||
return;
|
||||
}
|
||||
const touch = ev.touches[0] || (ev.changedTouches && ev.changedTouches[0]);
|
||||
if (!touch) return;
|
||||
if (!touch) {
|
||||
return;
|
||||
}
|
||||
const atLeftEdge = touch.clientX <= NavGestureEdgeBand;
|
||||
const atRightEdge = touch.clientX >= window.innerWidth - NavGestureEdgeBand;
|
||||
const atTopEdge = touch.clientY <= NavGestureEdgeBand;
|
||||
if (!atLeftEdge && !atRightEdge && !atTopEdge) return;
|
||||
if (isInteractiveTarget(ev.target)) return;
|
||||
if (!atLeftEdge && !atRightEdge && !atTopEdge) {
|
||||
return;
|
||||
}
|
||||
if (isInteractiveTarget(ev.target)) {
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -170,9 +170,15 @@ export default {
|
|||
},
|
||||
|
||||
getChipIcon(item) {
|
||||
if (item.action === "add") return "mdi-plus";
|
||||
if (item.action === "remove") return "mdi-minus";
|
||||
if (item.mixed) return "mdi-circle-half-full";
|
||||
if (item.action === "add") {
|
||||
return "mdi-plus";
|
||||
}
|
||||
if (item.action === "remove") {
|
||||
return "mdi-minus";
|
||||
}
|
||||
if (item.mixed) {
|
||||
return "mdi-circle-half-full";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
|
|
@ -188,7 +194,9 @@ export default {
|
|||
},
|
||||
|
||||
handleChipClick(item) {
|
||||
if (this.loading || this.disabled) return;
|
||||
if (this.loading || this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newAction;
|
||||
|
||||
|
|
@ -255,14 +263,20 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!title) return;
|
||||
if (!title) {
|
||||
return;
|
||||
}
|
||||
|
||||
let resolvedApplied = false;
|
||||
if (typeof this.resolveItemFromText === "function") {
|
||||
const resolved = this.resolveItemFromText(title);
|
||||
if (resolved && typeof resolved === "object") {
|
||||
if (resolved.title) title = resolved.title;
|
||||
if (resolved.value) value = resolved.value;
|
||||
if (resolved.title) {
|
||||
title = resolved.title;
|
||||
}
|
||||
if (resolved.value) {
|
||||
value = resolved.value;
|
||||
}
|
||||
resolvedApplied = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1564,7 +1564,9 @@ export default {
|
|||
}
|
||||
|
||||
const ok = await this.confirmDiscardSidebar();
|
||||
if (!ok) return;
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.closing = true;
|
||||
|
||||
|
|
@ -1595,7 +1597,9 @@ export default {
|
|||
// onChange().
|
||||
wrapPswpNavGuards() {
|
||||
const pswp = this.pswp();
|
||||
if (!pswp || pswp.__navGuardsInstalled) return;
|
||||
if (!pswp || pswp.__navGuardsInstalled) {
|
||||
return;
|
||||
}
|
||||
const origPrev = pswp.prev ? pswp.prev.bind(pswp) : null;
|
||||
const origNext = pswp.next ? pswp.next.bind(pswp) : null;
|
||||
if (origPrev) {
|
||||
|
|
@ -1605,7 +1609,9 @@ export default {
|
|||
return origPrev();
|
||||
}
|
||||
const ok = await this.confirmDiscardSidebar();
|
||||
if (!ok) return;
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
this._suppressNavCheck = true;
|
||||
return origPrev();
|
||||
};
|
||||
|
|
@ -1617,7 +1623,9 @@ export default {
|
|||
return origNext();
|
||||
}
|
||||
const ok = await this.confirmDiscardSidebar();
|
||||
if (!ok) return;
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
this._suppressNavCheck = true;
|
||||
return origNext();
|
||||
};
|
||||
|
|
@ -1749,7 +1757,9 @@ export default {
|
|||
if (!ok) {
|
||||
this._suppressNavCheck = true;
|
||||
const p = this.pswp();
|
||||
if (p && typeof p.goTo === "function") p.goTo(rollbackIndex);
|
||||
if (p && typeof p.goTo === "function") {
|
||||
p.goTo(rollbackIndex);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -1881,10 +1891,14 @@ export default {
|
|||
// the freshly-saved row; the overlay re-renders via the `markers`
|
||||
// computed.
|
||||
onCreateFaceMarker(area) {
|
||||
if (!this.photo.UID || !this.shouldShowEditButton() || this.faceMarkers.busy) return;
|
||||
if (!this.photo.UID || !this.shouldShowEditButton() || this.faceMarkers.busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
const file = Array.isArray(this.photo.Files) ? this.photo.Files.find((f) => !!f.Primary) : null;
|
||||
if (!file || !file.UID) return;
|
||||
if (!file || !file.UID) {
|
||||
return;
|
||||
}
|
||||
|
||||
const marker = new Marker({
|
||||
FileUID: file.UID,
|
||||
|
|
@ -1900,7 +1914,9 @@ export default {
|
|||
marker
|
||||
.save()
|
||||
.then(() => {
|
||||
if (!file.Markers) file.Markers = [];
|
||||
if (!file.Markers) {
|
||||
file.Markers = [];
|
||||
}
|
||||
file.Markers.push(marker.getValues());
|
||||
Photo.evictCache(this.photo.UID);
|
||||
// Trigger inline naming on the fresh row in the sidebar.
|
||||
|
|
@ -1927,8 +1943,12 @@ export default {
|
|||
// computed, which re-reads `photo.getMarkers(true)` whenever the
|
||||
// underlying `file.Markers` array is mutated.
|
||||
onEjectFaceMarker(marker) {
|
||||
if (!this.photo.UID || !this.shouldShowEditButton() || this.faceMarkers.busy) return;
|
||||
if (!marker || !marker.SubjUID || typeof marker.clearSubject !== "function") return;
|
||||
if (!this.photo.UID || !this.shouldShowEditButton() || this.faceMarkers.busy) {
|
||||
return;
|
||||
}
|
||||
if (!marker || !marker.SubjUID || typeof marker.clearSubject !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.faceMarkers.setBusy(true);
|
||||
marker
|
||||
|
|
@ -1950,9 +1970,13 @@ export default {
|
|||
// leave `file.Markers[idx]` (the raw object the overlay re-derives
|
||||
// from via `photo.getMarkers(true)`) stale.
|
||||
syncMarkerInFile(marker) {
|
||||
if (!marker || !marker.UID || !this.photo.UID || !Array.isArray(this.photo.Files)) return;
|
||||
if (!marker || !marker.UID || !this.photo.UID || !Array.isArray(this.photo.Files)) {
|
||||
return;
|
||||
}
|
||||
const file = this.photo.Files.find((f) => !!f.Primary);
|
||||
if (!file || !Array.isArray(file.Markers)) return;
|
||||
if (!file || !Array.isArray(file.Markers)) {
|
||||
return;
|
||||
}
|
||||
const idx = file.Markers.findIndex((mm) => mm.UID === marker.UID);
|
||||
if (idx >= 0) {
|
||||
file.Markers[idx] = typeof marker.getValues === "function" ? marker.getValues() : { ...file.Markers[idx], ...marker };
|
||||
|
|
@ -1963,8 +1987,12 @@ export default {
|
|||
// cache so future reads see the updated row; the overlay re-renders
|
||||
// via the `markers` computed.
|
||||
onReloadFaceMarkers(marker) {
|
||||
if (marker) this.syncMarkerInFile(marker);
|
||||
if (this.photo.UID) Photo.evictCache(this.photo.UID);
|
||||
if (marker) {
|
||||
this.syncMarkerInFile(marker);
|
||||
}
|
||||
if (this.photo.UID) {
|
||||
Photo.evictCache(this.photo.UID);
|
||||
}
|
||||
},
|
||||
// Handles the overlay's `remove` emit (✓ on the inline confirm pill
|
||||
// that appears when the user clicks an unnamed marker in edit mode).
|
||||
|
|
@ -1974,8 +2002,12 @@ export default {
|
|||
// never reach this handler — the overlay's hit-test skips them and
|
||||
// the backend gate (`marker.SubjUID` truthy) is a defense in depth.
|
||||
onRemoveFaceMarker(marker) {
|
||||
if (!this.photo.UID || !this.shouldShowEditButton() || this.faceMarkers.busy) return;
|
||||
if (!marker || marker.SubjUID || typeof marker.reject !== "function") return;
|
||||
if (!this.photo.UID || !this.shouldShowEditButton() || this.faceMarkers.busy) {
|
||||
return;
|
||||
}
|
||||
if (!marker || marker.SubjUID || typeof marker.reject !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
const file = Array.isArray(this.photo.Files) ? this.photo.Files.find((f) => !!f.Primary) : null;
|
||||
const uid = marker.UID;
|
||||
|
|
@ -1986,7 +2018,9 @@ export default {
|
|||
.then(() => {
|
||||
if (file && Array.isArray(file.Markers) && uid) {
|
||||
const idx = file.Markers.findIndex((mm) => mm.UID === uid);
|
||||
if (idx >= 0) file.Markers.splice(idx, 1);
|
||||
if (idx >= 0) {
|
||||
file.Markers.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
Photo.evictCache(this.photo.UID);
|
||||
})
|
||||
|
|
@ -2568,7 +2602,9 @@ export default {
|
|||
// typed into sidebar inputs. `preventDefault` makes `_onKeyDown`
|
||||
// bail before its switch statement.
|
||||
onPswpKeyDown(ev) {
|
||||
if (!ev || !this.info) return;
|
||||
if (!ev || !this.info) {
|
||||
return;
|
||||
}
|
||||
const active = document.activeElement;
|
||||
if (active instanceof HTMLInputElement || active instanceof HTMLTextAreaElement || (active && active.isContentEditable)) {
|
||||
ev.preventDefault();
|
||||
|
|
@ -2579,7 +2615,9 @@ export default {
|
|||
// navigation reaches sidebar inputs/chips. Vuetify + `$view` re-anchor
|
||||
// any focus that escapes the modal.
|
||||
onTabKey(ev) {
|
||||
if (!ev) return;
|
||||
if (!ev) {
|
||||
return;
|
||||
}
|
||||
const root = this.$refs.container || this.$refs.content;
|
||||
const active = document.activeElement;
|
||||
if (root && active && root.contains(active)) {
|
||||
|
|
@ -2989,7 +3027,9 @@ export default {
|
|||
}
|
||||
|
||||
const ok = await this.confirmDiscardSidebar();
|
||||
if (!ok) return;
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.info = false;
|
||||
// Restore the user's persisted caption preference. The sidebar
|
||||
|
|
|
|||
|
|
@ -177,7 +177,9 @@ export default {
|
|||
this.$view.leave(this);
|
||||
},
|
||||
loadFromPhoto() {
|
||||
if (!this.photo) return;
|
||||
if (!this.photo) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.cameraID = this.photo.CameraID || 0;
|
||||
this.lensID = this.photo.LensID || 0;
|
||||
|
|
|
|||
|
|
@ -179,7 +179,9 @@ export default {
|
|||
this.$view.leave(this);
|
||||
},
|
||||
loadFromPhoto() {
|
||||
if (!this.photo) return;
|
||||
if (!this.photo) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.day = this.photo.Day;
|
||||
this.month = this.photo.Month;
|
||||
|
|
@ -191,17 +193,23 @@ export default {
|
|||
this.invalidDate = false;
|
||||
},
|
||||
effectiveYear() {
|
||||
if (this.year > 0) return this.year;
|
||||
if (this.year > 0) {
|
||||
return this.year;
|
||||
}
|
||||
const y = this.photo?.TakenAtLocal ? parseInt(this.photo.TakenAtLocal.substring(0, 4)) : new Date().getUTCFullYear();
|
||||
return isNaN(y) ? new Date().getUTCFullYear() : y;
|
||||
},
|
||||
effectiveMonth() {
|
||||
if (this.month > 0) return this.month;
|
||||
if (this.month > 0) {
|
||||
return this.month;
|
||||
}
|
||||
const m = this.photo?.TakenAtLocal ? parseInt(this.photo.TakenAtLocal.substring(5, 7)) : new Date().getUTCMonth() + 1;
|
||||
return isNaN(m) ? new Date().getUTCMonth() + 1 : m;
|
||||
},
|
||||
clampDayToValidRange() {
|
||||
if (this.day <= 0) return;
|
||||
if (this.day <= 0) {
|
||||
return;
|
||||
}
|
||||
const maxDay = new Date(Date.UTC(this.effectiveYear(), this.effectiveMonth(), 0)).getUTCDate();
|
||||
if (this.day > maxDay) {
|
||||
this.day = maxDay;
|
||||
|
|
@ -260,22 +268,32 @@ export default {
|
|||
this.updateLocalDate();
|
||||
},
|
||||
localYearString() {
|
||||
if (this.year <= 0) return "";
|
||||
if (this.year <= 0) {
|
||||
return "";
|
||||
}
|
||||
return this.year.toString().padStart(4, "0");
|
||||
},
|
||||
localMonthString() {
|
||||
if (this.month <= 0) return "01";
|
||||
if (this.month <= 0) {
|
||||
return "01";
|
||||
}
|
||||
return this.month.toString().padStart(2, "0");
|
||||
},
|
||||
localDayString() {
|
||||
if (this.day <= 0) return "01";
|
||||
if (this.day <= 0) {
|
||||
return "01";
|
||||
}
|
||||
return this.day.toString().padStart(2, "0");
|
||||
},
|
||||
updateLocalDate() {
|
||||
if (!this.photo) return;
|
||||
if (!this.photo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const yearStr = this.localYearString();
|
||||
if (!yearStr) return;
|
||||
if (!yearStr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const date = yearStr + "-" + this.localMonthString() + "-" + this.localDayString();
|
||||
const time = this.time || "12:00:00";
|
||||
|
|
@ -289,7 +307,9 @@ export default {
|
|||
this.$emit("close");
|
||||
},
|
||||
confirm() {
|
||||
if (this.invalidDate) return;
|
||||
if (this.invalidDate) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit("confirm", {
|
||||
Day: this.day,
|
||||
|
|
|
|||
|
|
@ -159,10 +159,14 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
isValidCoordinateInput() {
|
||||
if (!this.coordinateInput) return false;
|
||||
if (!this.coordinateInput) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const parts = this.coordinateInput.split(",").map((part) => part.trim());
|
||||
if (parts.length !== 2) return false;
|
||||
if (parts.length !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const lat = parseFloat(parts[0]);
|
||||
const lng = parseFloat(parts[1]);
|
||||
|
|
@ -216,7 +220,9 @@ export default {
|
|||
}
|
||||
},
|
||||
applyCoordinates() {
|
||||
if (!this.isValidCoordinateInput) return;
|
||||
if (!this.isValidCoordinateInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = this.coordinateInput.split(",").map((part) => part.trim());
|
||||
const lat = parseFloat(parts[0]);
|
||||
|
|
|
|||
|
|
@ -817,24 +817,34 @@ export default {
|
|||
}
|
||||
|
||||
const t = String(inputTitle).trim();
|
||||
if (!t) return null;
|
||||
if (!t) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const nt = $util.normalizeTitle(t);
|
||||
const st = $util.slugifyLabelTitle(t);
|
||||
|
||||
let found = this.cachedLabelOptions.find((o) => o.title.toLowerCase() === t.toLowerCase());
|
||||
if (found) return { value: found.value, title: found.title };
|
||||
if (found) {
|
||||
return { value: found.value, title: found.title };
|
||||
}
|
||||
|
||||
found = this.cachedLabelOptions.find((o) => o.slug === st || o.customSlug === st);
|
||||
if (found) return { value: found.value, title: found.title };
|
||||
if (found) {
|
||||
return { value: found.value, title: found.title };
|
||||
}
|
||||
|
||||
found = this.cachedLabelOptions.find((o) => $util.normalizeTitle(o.title) === nt);
|
||||
if (found) return { value: found.value, title: found.title };
|
||||
if (found) {
|
||||
return { value: found.value, title: found.title };
|
||||
}
|
||||
|
||||
return { value: "", title: t };
|
||||
},
|
||||
changeValue(newValue, fieldType, fieldName) {
|
||||
if (!fieldName) return;
|
||||
if (!fieldName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousValue = this.previousFormData[fieldName].value;
|
||||
this.formData[fieldName].action = this.actions.update;
|
||||
|
|
@ -860,7 +870,9 @@ export default {
|
|||
this.getIcon(fieldType, fieldName);
|
||||
},
|
||||
changeSelectValue(newValue, fieldType, fieldName) {
|
||||
if (!fieldName) return;
|
||||
if (!fieldName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousValue = this.previousFormData[fieldName].value;
|
||||
this.formData[fieldName].action = this.actions.update;
|
||||
|
|
@ -917,11 +929,15 @@ export default {
|
|||
return y > 0 && m > 0 && !yearMixed && !monthMixed;
|
||||
},
|
||||
clampBatchDayIfResolvable() {
|
||||
if (!this.isBatchDateResolvable()) return;
|
||||
if (!this.isBatchDateResolvable()) {
|
||||
return;
|
||||
}
|
||||
const y = this.batchYear();
|
||||
const m = this.batchMonth();
|
||||
const d = parseInt(this.formData?.Day?.value, 10);
|
||||
if (isNaN(d) || d <= 0) return; // Unknown or empty: do nothing in batch UI
|
||||
if (isNaN(d) || d <= 0) {
|
||||
return;
|
||||
} // Unknown or empty: do nothing in batch UI
|
||||
const maxDay = new Date(Date.UTC(y, m, 0)).getUTCDate();
|
||||
if (d > maxDay) {
|
||||
this.formData.Day.value = maxDay;
|
||||
|
|
@ -929,7 +945,9 @@ export default {
|
|||
}
|
||||
},
|
||||
changeToggleValue(newValue, fieldName) {
|
||||
if (!fieldName) return;
|
||||
if (!fieldName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousValue = this.previousFormData[fieldName].value;
|
||||
this.formData[fieldName].action = this.actions.update;
|
||||
|
|
@ -976,7 +994,9 @@ export default {
|
|||
const formKey = key || name;
|
||||
const fieldData = this.values[formKey];
|
||||
|
||||
if (!fieldData) return;
|
||||
if (!fieldData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { value, placeholder } = this.getFieldData(type, name);
|
||||
this.formData[formKey] = {
|
||||
|
|
@ -1004,7 +1024,9 @@ export default {
|
|||
// Set values for toggle fields (boolean fields)
|
||||
this.toggleFieldsArray.forEach((fieldName) => {
|
||||
const fieldData = this.values[fieldName];
|
||||
if (!fieldData) return;
|
||||
if (!fieldData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toggleValue = this.getToggleValue(fieldName);
|
||||
|
||||
|
|
@ -1062,7 +1084,9 @@ export default {
|
|||
},
|
||||
toggleOptions(fieldName) {
|
||||
const fieldData = this.values[fieldName];
|
||||
if (!fieldData) return [];
|
||||
if (!fieldData) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const result = [
|
||||
{ text: this.$gettext("Yes"), value: true },
|
||||
|
|
@ -1116,7 +1140,9 @@ export default {
|
|||
const fieldData = this.values[fieldName];
|
||||
const isDeleted = this.deletedFields?.[fieldName];
|
||||
|
||||
if (!fieldData) return;
|
||||
if (!fieldData) {
|
||||
return;
|
||||
}
|
||||
const previousField = this.previousFormData[fieldName];
|
||||
|
||||
if (this.formData[fieldName].value !== previousField?.value || isDeleted) {
|
||||
|
|
@ -1133,7 +1159,9 @@ export default {
|
|||
const fieldData = this.values[fieldName];
|
||||
const isDeleted = this.deletedFields?.[fieldName];
|
||||
|
||||
if (!fieldData) return { value: "", placeholder: "", persistent: false };
|
||||
if (!fieldData) {
|
||||
return { value: "", placeholder: "", persistent: false };
|
||||
}
|
||||
|
||||
// Helper function to format numeric values
|
||||
const formatNumericValue = (value) => {
|
||||
|
|
|
|||
|
|
@ -531,20 +531,26 @@ export default {
|
|||
},
|
||||
// Returns the effective year used for validation: explicit year or from TakenAtLocal if unknown
|
||||
effectiveYear() {
|
||||
if (this.view?.model?.Year && this.view.model.Year > 0) return this.view.model.Year;
|
||||
if (this.view?.model?.Year && this.view.model.Year > 0) {
|
||||
return this.view.model.Year;
|
||||
}
|
||||
const y = this.view?.model?.TakenAtLocal ? parseInt(this.view.model.TakenAtLocal.substring(0, 4)) : new Date().getUTCFullYear();
|
||||
return isNaN(y) ? new Date().getUTCFullYear() : y;
|
||||
},
|
||||
// Returns the effective month used for validation: explicit month or from TakenAtLocal if unknown
|
||||
effectiveMonth() {
|
||||
if (this.view?.model?.Month && this.view.model.Month > 0) return this.view.model.Month;
|
||||
if (this.view?.model?.Month && this.view.model.Month > 0) {
|
||||
return this.view.model.Month;
|
||||
}
|
||||
const m = this.view?.model?.TakenAtLocal ? parseInt(this.view.model.TakenAtLocal.substring(5, 7)) : new Date().getUTCMonth() + 1;
|
||||
return isNaN(m) ? new Date().getUTCMonth() + 1 : m;
|
||||
},
|
||||
// Clamp day to the maximum valid day of the current effective month/year
|
||||
clampDayToValidRange() {
|
||||
const day = this.view?.model?.Day || 0;
|
||||
if (day <= 0) return; // Unknown day stays unknown
|
||||
if (day <= 0) {
|
||||
return;
|
||||
} // Unknown day stays unknown
|
||||
const y = this.effectiveYear();
|
||||
const m = this.effectiveMonth();
|
||||
// JS Date trick: day 0 of next month yields last day of current month
|
||||
|
|
|
|||
|
|
@ -170,7 +170,9 @@ export default {
|
|||
}
|
||||
},
|
||||
onReject(model) {
|
||||
if (this.busy || !model) return;
|
||||
if (this.busy || !model) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.busy = true;
|
||||
this.$notify.blockUI("busy");
|
||||
|
|
@ -315,14 +317,18 @@ export default {
|
|||
});
|
||||
},
|
||||
onApprove(model) {
|
||||
if (this.busy || !model) return;
|
||||
if (this.busy || !model) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.busy = true;
|
||||
|
||||
model.approve().finally(() => (this.busy = false));
|
||||
},
|
||||
onClearSubject(model) {
|
||||
if (this.busy || !model) return;
|
||||
if (this.busy || !model) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.busy = true;
|
||||
this.$notify.blockUI("busy");
|
||||
|
|
|
|||
|
|
@ -174,7 +174,9 @@ export default {
|
|||
return this.mode === FaceMarkerEdit;
|
||||
},
|
||||
svgStyle() {
|
||||
if (!this.bounds) return { display: "none" };
|
||||
if (!this.bounds) {
|
||||
return { display: "none" };
|
||||
}
|
||||
return {
|
||||
position: "absolute",
|
||||
left: `${this.bounds.left}px`,
|
||||
|
|
@ -190,7 +192,9 @@ export default {
|
|||
return this.hoverCursor ? { cursor: this.hoverCursor } : {};
|
||||
},
|
||||
confirmStyle() {
|
||||
if (!this.pending || !this.bounds) return { display: "none" };
|
||||
if (!this.pending || !this.bounds) {
|
||||
return { display: "none" };
|
||||
}
|
||||
const left = this.bounds.left + this.pending.x + this.pending.w / 2;
|
||||
const top = this.bounds.top + this.pending.y + this.pending.h;
|
||||
return {
|
||||
|
|
@ -204,7 +208,9 @@ export default {
|
|||
// coordinate space. Used to anchor the remove-confirm pill and to
|
||||
// highlight the target rectangle.
|
||||
removingMarkerRect() {
|
||||
if (!this.removingMarker || !this.bounds) return null;
|
||||
if (!this.removingMarker || !this.bounds) {
|
||||
return null;
|
||||
}
|
||||
const m = this.removingMarker;
|
||||
return {
|
||||
x: m.X * this.bounds.width,
|
||||
|
|
@ -215,7 +221,9 @@ export default {
|
|||
},
|
||||
removeConfirmStyle() {
|
||||
const r = this.removingMarkerRect;
|
||||
if (!r || !this.bounds) return { display: "none" };
|
||||
if (!r || !this.bounds) {
|
||||
return { display: "none" };
|
||||
}
|
||||
const left = this.bounds.left + r.x + r.w / 2;
|
||||
const top = this.bounds.top + r.y + r.h;
|
||||
return {
|
||||
|
|
@ -270,7 +278,9 @@ export default {
|
|||
methods: {
|
||||
imageElement() {
|
||||
const el = this.pswp?.currSlide?.content?.element;
|
||||
if (el instanceof HTMLImageElement) return el;
|
||||
if (el instanceof HTMLImageElement) {
|
||||
return el;
|
||||
}
|
||||
if (el && typeof el.querySelector === "function") {
|
||||
return el.querySelector("img.pswp__image");
|
||||
}
|
||||
|
|
@ -287,7 +297,9 @@ export default {
|
|||
this._loadListenedImg = null;
|
||||
return;
|
||||
}
|
||||
if (this._loadListenedImg === img) return;
|
||||
if (this._loadListenedImg === img) {
|
||||
return;
|
||||
}
|
||||
this.detachImageLoadListener();
|
||||
this._loadListenedImg = img;
|
||||
this._onImgLoad = () => this.scheduleUpdate();
|
||||
|
|
@ -301,7 +313,9 @@ export default {
|
|||
this._onImgLoad = null;
|
||||
},
|
||||
attachPswpListeners() {
|
||||
if (!this.pswp || typeof this.pswp.on !== "function") return;
|
||||
if (!this.pswp || typeof this.pswp.on !== "function") {
|
||||
return;
|
||||
}
|
||||
this._onZoomPan = () => this.scheduleUpdate();
|
||||
this._onChange = () => {
|
||||
this.attachImageLoadListener();
|
||||
|
|
@ -314,16 +328,24 @@ export default {
|
|||
this.pswp.on("imageClickAction", this._onChange);
|
||||
},
|
||||
detachPswpListeners() {
|
||||
if (!this.pswp || typeof this.pswp.off !== "function") return;
|
||||
if (this._onZoomPan) this.pswp.off("zoomPanUpdate", this._onZoomPan);
|
||||
if (!this.pswp || typeof this.pswp.off !== "function") {
|
||||
return;
|
||||
}
|
||||
if (this._onZoomPan) {
|
||||
this.pswp.off("zoomPanUpdate", this._onZoomPan);
|
||||
}
|
||||
if (this._onChange) {
|
||||
this.pswp.off("change", this._onChange);
|
||||
this.pswp.off("imageClickAction", this._onChange);
|
||||
}
|
||||
if (this._onResize) this.pswp.off("resize", this._onResize);
|
||||
if (this._onResize) {
|
||||
this.pswp.off("resize", this._onResize);
|
||||
}
|
||||
},
|
||||
scheduleUpdate() {
|
||||
if (this.rafHandle) return;
|
||||
if (this.rafHandle) {
|
||||
return;
|
||||
}
|
||||
this.rafHandle = requestAnimationFrame(() => {
|
||||
this.rafHandle = null;
|
||||
this.updateBounds();
|
||||
|
|
@ -332,13 +354,17 @@ export default {
|
|||
updateBounds() {
|
||||
const img = this.imageElement();
|
||||
if (!img || !this.$refs.root) {
|
||||
if (this.bounds !== null) this.bounds = null;
|
||||
if (this.bounds !== null) {
|
||||
this.bounds = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const imgRect = img.getBoundingClientRect();
|
||||
const parentRect = this.$refs.root.getBoundingClientRect();
|
||||
if (imgRect.width <= 0 || imgRect.height <= 0) {
|
||||
if (this.bounds !== null) this.bounds = null;
|
||||
if (this.bounds !== null) {
|
||||
this.bounds = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// getBoundingClientRect returns the <img> box, not the letterboxed pixel
|
||||
|
|
@ -378,17 +404,25 @@ export default {
|
|||
this.bounds = { left, top, width, height };
|
||||
},
|
||||
onPointerDown(ev) {
|
||||
if (!this.isEditMode) return;
|
||||
if (!this.isEditMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.bounds) {
|
||||
this.updateBounds();
|
||||
if (!this.bounds) return;
|
||||
if (!this.bounds) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.button !== undefined && ev.button !== 0) return;
|
||||
if (ev.button !== undefined && ev.button !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const local = this.toLocal(ev.clientX, ev.clientY);
|
||||
if (!this.insideBounds(local)) return;
|
||||
if (!this.insideBounds(local)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.pending) {
|
||||
const corner = this.hitTestCorner(local, this.pending);
|
||||
|
|
@ -415,7 +449,9 @@ export default {
|
|||
// Clicking outside a marker cancels any pending remove pill so a
|
||||
// fresh draw can start from the same gesture without a prior
|
||||
// click "stealing" focus.
|
||||
if (this.removingMarker) this.removingMarker = null;
|
||||
if (this.removingMarker) {
|
||||
this.removingMarker = null;
|
||||
}
|
||||
|
||||
this.stopEventFromPswp(ev);
|
||||
this.pending = null;
|
||||
|
|
@ -429,16 +465,22 @@ export default {
|
|||
// Returns the first unnamed marker whose pixel rect contains the
|
||||
// given local point, or null if none. Named markers are skipped.
|
||||
findMarkerAt(local) {
|
||||
if (!this.bounds || !Array.isArray(this.markers)) return null;
|
||||
if (!this.bounds || !Array.isArray(this.markers)) {
|
||||
return null;
|
||||
}
|
||||
for (const m of this.markers) {
|
||||
if (!m || m.SubjUID) continue;
|
||||
if (!m || m.SubjUID) {
|
||||
continue;
|
||||
}
|
||||
const rect = {
|
||||
x: m.X * this.bounds.width,
|
||||
y: m.Y * this.bounds.height,
|
||||
w: m.W * this.bounds.width,
|
||||
h: m.H * this.bounds.height,
|
||||
};
|
||||
if (this.insidePending(local, rect)) return m;
|
||||
if (this.insidePending(local, rect)) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
|
@ -447,7 +489,9 @@ export default {
|
|||
// from the updated photo state.
|
||||
onConfirmRemove() {
|
||||
const m = this.removingMarker;
|
||||
if (!m) return;
|
||||
if (!m) {
|
||||
return;
|
||||
}
|
||||
this.removingMarker = null;
|
||||
this.$emit("remove", m);
|
||||
},
|
||||
|
|
@ -456,8 +500,12 @@ export default {
|
|||
this.removingMarker = null;
|
||||
},
|
||||
onPointerMove(ev) {
|
||||
if (!this.interaction || !this.dragStart || !this.bounds) return;
|
||||
if (this.pointerId !== null && ev.pointerId !== this.pointerId) return;
|
||||
if (!this.interaction || !this.dragStart || !this.bounds) {
|
||||
return;
|
||||
}
|
||||
if (this.pointerId !== null && ev.pointerId !== this.pointerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const local = this.toLocal(ev.clientX, ev.clientY);
|
||||
const cx = Math.max(0, Math.min(this.bounds.width, local.x));
|
||||
|
|
@ -465,15 +513,25 @@ export default {
|
|||
|
||||
if (this.interaction === InteractionMove) {
|
||||
const origin = this.dragStart.pending;
|
||||
if (!origin) return;
|
||||
if (!origin) {
|
||||
return;
|
||||
}
|
||||
const dx = local.x - this.dragStart.local.x;
|
||||
const dy = local.y - this.dragStart.local.y;
|
||||
let nx = origin.x + dx;
|
||||
let ny = origin.y + dy;
|
||||
if (nx < 0) nx = 0;
|
||||
if (ny < 0) ny = 0;
|
||||
if (nx + origin.w > this.bounds.width) nx = this.bounds.width - origin.w;
|
||||
if (ny + origin.h > this.bounds.height) ny = this.bounds.height - origin.h;
|
||||
if (nx < 0) {
|
||||
nx = 0;
|
||||
}
|
||||
if (ny < 0) {
|
||||
ny = 0;
|
||||
}
|
||||
if (nx + origin.w > this.bounds.width) {
|
||||
nx = this.bounds.width - origin.w;
|
||||
}
|
||||
if (ny + origin.h > this.bounds.height) {
|
||||
ny = this.bounds.height - origin.h;
|
||||
}
|
||||
this.pending = { x: nx, y: ny, w: origin.w, h: origin.h };
|
||||
return;
|
||||
}
|
||||
|
|
@ -497,8 +555,12 @@ export default {
|
|||
let sw = side;
|
||||
let sh = side;
|
||||
|
||||
if (signX < 0) sx = this.dragStart.local.x - side;
|
||||
if (signY < 0) sy = this.dragStart.local.y - side;
|
||||
if (signX < 0) {
|
||||
sx = this.dragStart.local.x - side;
|
||||
}
|
||||
if (signY < 0) {
|
||||
sy = this.dragStart.local.y - side;
|
||||
}
|
||||
|
||||
if (sx < 0) {
|
||||
sw += sx;
|
||||
|
|
@ -521,8 +583,12 @@ export default {
|
|||
sh -= over;
|
||||
}
|
||||
|
||||
if (sw < 0) sw = 0;
|
||||
if (sh < 0) sh = 0;
|
||||
if (sw < 0) {
|
||||
sw = 0;
|
||||
}
|
||||
if (sh < 0) {
|
||||
sh = 0;
|
||||
}
|
||||
|
||||
if (this.interaction === InteractionResize) {
|
||||
this.pending = { x: sx, y: sy, w: sw, h: sh };
|
||||
|
|
@ -531,8 +597,12 @@ export default {
|
|||
}
|
||||
},
|
||||
onPointerUp(ev) {
|
||||
if (!this.interaction) return;
|
||||
if (this.pointerId !== null && ev && ev.pointerId !== this.pointerId) return;
|
||||
if (!this.interaction) {
|
||||
return;
|
||||
}
|
||||
if (this.pointerId !== null && ev && ev.pointerId !== this.pointerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.detachWindowPointerListeners();
|
||||
|
||||
|
|
@ -547,7 +617,9 @@ export default {
|
|||
|
||||
// Move/resize have already written the up-to-date `pending`; only
|
||||
// the draw path needs to promote its draft into pending.
|
||||
if (wasInteraction !== InteractionDraw) return;
|
||||
if (wasInteraction !== InteractionDraw) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!draft || !this.bounds || draft.w < MIN_DRAW_SIZE || draft.h < MIN_DRAW_SIZE) {
|
||||
return;
|
||||
|
|
@ -556,11 +628,15 @@ export default {
|
|||
this.pending = draft;
|
||||
},
|
||||
onConfirmPending() {
|
||||
if (this.busy) return;
|
||||
if (this.busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pending = this.pending;
|
||||
const bounds = this.bounds;
|
||||
if (!pending || !bounds) return;
|
||||
if (!pending || !bounds) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nx = pending.x / bounds.width;
|
||||
const ny = pending.y / bounds.height;
|
||||
|
|
@ -607,7 +683,9 @@ export default {
|
|||
},
|
||||
// handleEnter mirrors a ✓ click; no-op during draft / drag / remove-confirm.
|
||||
handleEnter() {
|
||||
if (this.busy || this.interaction || this.removingMarker || !this.pending) return;
|
||||
if (this.busy || this.interaction || this.removingMarker || !this.pending) {
|
||||
return;
|
||||
}
|
||||
this.onConfirmPending();
|
||||
},
|
||||
// handleEscape cancels in-progress draw/move/resize or clears the pending
|
||||
|
|
@ -623,7 +701,9 @@ export default {
|
|||
}
|
||||
if (this.interaction === InteractionMove || this.interaction === InteractionResize) {
|
||||
const snapshot = this.dragStart && this.dragStart.pending;
|
||||
if (snapshot) this.pending = { ...snapshot };
|
||||
if (snapshot) {
|
||||
this.pending = { ...snapshot };
|
||||
}
|
||||
this.interaction = null;
|
||||
this.resizeCorner = null;
|
||||
this.pointerId = null;
|
||||
|
|
@ -642,8 +722,12 @@ export default {
|
|||
return false;
|
||||
},
|
||||
stopEventFromPswp(ev) {
|
||||
if (typeof ev.stopPropagation === "function") ev.stopPropagation();
|
||||
if (typeof ev.preventDefault === "function" && ev.cancelable !== false) ev.preventDefault();
|
||||
if (typeof ev.stopPropagation === "function") {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
if (typeof ev.preventDefault === "function" && ev.cancelable !== false) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
},
|
||||
attachWindowPointerListeners() {
|
||||
window.addEventListener("pointermove", this.onPointerMove);
|
||||
|
|
@ -665,7 +749,9 @@ export default {
|
|||
};
|
||||
for (const key of Object.keys(corners)) {
|
||||
const c = corners[key];
|
||||
if (Math.hypot(p.x - c.x, p.y - c.y) <= r) return key;
|
||||
if (Math.hypot(p.x - c.x, p.y - c.y) <= r) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
|
@ -676,12 +762,19 @@ export default {
|
|||
// math in onPointerMove works the same way as for the draw path.
|
||||
beginResize(corner, ev) {
|
||||
const p = this.pending;
|
||||
if (!p) return;
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
let anchor;
|
||||
if (corner === "tl") anchor = { x: p.x + p.w, y: p.y + p.h };
|
||||
else if (corner === "tr") anchor = { x: p.x, y: p.y + p.h };
|
||||
else if (corner === "bl") anchor = { x: p.x + p.w, y: p.y };
|
||||
else anchor = { x: p.x, y: p.y };
|
||||
if (corner === "tl") {
|
||||
anchor = { x: p.x + p.w, y: p.y + p.h };
|
||||
} else if (corner === "tr") {
|
||||
anchor = { x: p.x, y: p.y + p.h };
|
||||
} else if (corner === "bl") {
|
||||
anchor = { x: p.x + p.w, y: p.y };
|
||||
} else {
|
||||
anchor = { x: p.x, y: p.y };
|
||||
}
|
||||
|
||||
this.stopEventFromPswp(ev);
|
||||
this.interaction = InteractionResize;
|
||||
|
|
@ -696,45 +789,65 @@ export default {
|
|||
this.attachWindowPointerListeners();
|
||||
},
|
||||
onHoverMove(ev) {
|
||||
if (!this.isEditMode || this.interaction) return;
|
||||
if (!this.isEditMode || this.interaction) {
|
||||
return;
|
||||
}
|
||||
if (!this.bounds) {
|
||||
if (this.hoverCursor !== null) this.hoverCursor = null;
|
||||
if (this.hoverCursor !== null) {
|
||||
this.hoverCursor = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const local = this.toLocal(ev.clientX, ev.clientY);
|
||||
if (!this.insideBounds(local)) {
|
||||
if (this.hoverCursor !== null) this.hoverCursor = null;
|
||||
if (this.hoverCursor !== null) {
|
||||
this.hoverCursor = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.pending) {
|
||||
const corner = this.hitTestCorner(local, this.pending);
|
||||
if (corner) {
|
||||
const c = corner === "tl" || corner === "br" ? "nwse-resize" : "nesw-resize";
|
||||
if (this.hoverCursor !== c) this.hoverCursor = c;
|
||||
if (this.hoverCursor !== c) {
|
||||
this.hoverCursor = c;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.insidePending(local, this.pending)) {
|
||||
if (this.hoverCursor !== "move") this.hoverCursor = "move";
|
||||
if (this.hoverCursor !== "move") {
|
||||
this.hoverCursor = "move";
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Hovering an unnamed marker rect: signal it is clickable for
|
||||
// removal. Named markers fall through to the default cursor.
|
||||
if (this.findMarkerAt(local)) {
|
||||
if (this.hoverCursor !== "pointer") this.hoverCursor = "pointer";
|
||||
if (this.hoverCursor !== "pointer") {
|
||||
this.hoverCursor = "pointer";
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.hoverCursor !== null) this.hoverCursor = null;
|
||||
if (this.hoverCursor !== null) {
|
||||
this.hoverCursor = null;
|
||||
}
|
||||
},
|
||||
onHoverLeave() {
|
||||
if (this.hoverCursor !== null) this.hoverCursor = null;
|
||||
if (this.hoverCursor !== null) {
|
||||
this.hoverCursor = null;
|
||||
}
|
||||
},
|
||||
// onWheel re-dispatches wheel events on PhotoSwipe's element while in edit
|
||||
// mode (overlay's pointer-events: auto would otherwise swallow zoom gestures).
|
||||
onWheel(ev) {
|
||||
if (!this.isEditMode) return;
|
||||
if (!this.isEditMode) {
|
||||
return;
|
||||
}
|
||||
const pswpEl = this.pswp?.element;
|
||||
if (!pswpEl) return;
|
||||
if (!pswpEl) {
|
||||
return;
|
||||
}
|
||||
if (typeof ev.preventDefault === "function" && ev.cancelable !== false) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
|
@ -757,7 +870,9 @@ export default {
|
|||
},
|
||||
beginMove(local, ev) {
|
||||
const p = this.pending;
|
||||
if (!p) return;
|
||||
if (!p) {
|
||||
return;
|
||||
}
|
||||
this.stopEventFromPswp(ev);
|
||||
this.interaction = InteractionMove;
|
||||
this.resizeCorner = null;
|
||||
|
|
@ -771,7 +886,9 @@ export default {
|
|||
this.attachWindowPointerListeners();
|
||||
},
|
||||
toLocal(clientX, clientY) {
|
||||
if (!this.bounds || !this.$refs.root) return { x: 0, y: 0 };
|
||||
if (!this.bounds || !this.$refs.root) {
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
const rect = this.$refs.root.getBoundingClientRect();
|
||||
return {
|
||||
x: clientX - rect.left - this.bounds.left,
|
||||
|
|
@ -782,8 +899,12 @@ export default {
|
|||
return this.bounds && p.x >= 0 && p.y >= 0 && p.x <= this.bounds.width && p.y <= this.bounds.height;
|
||||
},
|
||||
clamp01(v) {
|
||||
if (v < 0) return 0;
|
||||
if (v >= 1) return 0.999999;
|
||||
if (v < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (v >= 1) {
|
||||
return 0.999999;
|
||||
}
|
||||
return v;
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -288,7 +288,9 @@ export default {
|
|||
},
|
||||
watch: {
|
||||
search(q) {
|
||||
if (this.loading) return;
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
const exists = this.paths.findIndex((p) => p.abs === q);
|
||||
|
||||
|
|
|
|||
|
|
@ -660,15 +660,21 @@ export default {
|
|||
// `photo.Caption` as the empty-string default — must fall through to
|
||||
// `model.Caption`.
|
||||
const raw = this.photo?.Caption || this.model?.Caption;
|
||||
if (!raw) return "";
|
||||
if (!raw) {
|
||||
return "";
|
||||
}
|
||||
return this.$util.sanitizeHtml(this.$util.encodeHTML(raw));
|
||||
},
|
||||
notesHtml() {
|
||||
if (!this.photo?.Details?.Notes) return "";
|
||||
if (!this.photo?.Details?.Notes) {
|
||||
return "";
|
||||
}
|
||||
return this.$util.sanitizeHtml(this.$util.encodeHTML(this.photo.Details.Notes));
|
||||
},
|
||||
cameraInfo() {
|
||||
if (!this.photo) return "";
|
||||
if (!this.photo) {
|
||||
return "";
|
||||
}
|
||||
// Backend returns the "Unknown" placeholder camera (CameraID=1,
|
||||
// Camera={Make:"", Model:"Unknown"}) when no EXIF camera is set, and
|
||||
// formatCamera() happily renders that as " Unknown". Suppress it so
|
||||
|
|
@ -677,26 +683,38 @@ export default {
|
|||
(this.photo.CameraID && this.photo.CameraID > 1) ||
|
||||
(this.photo.CameraMake && this.photo.CameraMake.trim()) ||
|
||||
(this.photo.CameraModel && this.photo.CameraModel.trim() && this.photo.CameraModel !== "Unknown");
|
||||
if (!hasRealCamera) return "";
|
||||
if (!hasRealCamera) {
|
||||
return "";
|
||||
}
|
||||
// Suppress "Unknown, ISO 100"-style rows when only ISO/exposure are set.
|
||||
if (!this.$util.formatCamera(this.photo.Camera, this.photo.CameraID, this.photo.CameraMake, this.photo.CameraModel, false)) return "";
|
||||
if (!this.$util.formatCamera(this.photo.Camera, this.photo.CameraID, this.photo.CameraMake, this.photo.CameraModel, false)) {
|
||||
return "";
|
||||
}
|
||||
const info = this.photo.getCameraInfo();
|
||||
return info !== this.$gettext("Unknown") ? info : "";
|
||||
},
|
||||
lensInfo() {
|
||||
if (!this.photo) return "";
|
||||
if (!this.photo) {
|
||||
return "";
|
||||
}
|
||||
const hasLens =
|
||||
(this.photo.LensID && this.photo.LensID > 1) || this.photo.LensMake || this.photo.LensModel || this.photo.Lens?.Model || this.photo.Lens?.Make;
|
||||
if (!hasLens) return "";
|
||||
if (!hasLens) {
|
||||
return "";
|
||||
}
|
||||
const info = this.photo.getLensInfo();
|
||||
return info !== this.$gettext("Unknown") ? info : "";
|
||||
},
|
||||
exifInfo() {
|
||||
if (!this.photo) return "";
|
||||
if (!this.photo) {
|
||||
return "";
|
||||
}
|
||||
return this.photo.getExifInfo();
|
||||
},
|
||||
people() {
|
||||
if (!this.photo) return [];
|
||||
if (!this.photo) {
|
||||
return [];
|
||||
}
|
||||
return this.photo.getMarkers(true);
|
||||
},
|
||||
// Sorted, locale-aware copy of `$config.values.people` for the marker
|
||||
|
|
@ -704,14 +722,18 @@ export default {
|
|||
// `people.{created,updated,deleted}` events.
|
||||
knownPeople() {
|
||||
const values = this.$config && this.$config.values;
|
||||
if (!values || !Array.isArray(values.people)) return [];
|
||||
if (!values || !Array.isArray(values.people)) {
|
||||
return [];
|
||||
}
|
||||
return values.people
|
||||
.filter((p) => p && p.Name)
|
||||
.slice()
|
||||
.sort((a, b) => (a.Name || "").localeCompare(b.Name || "", undefined, { sensitivity: "base", numeric: true }));
|
||||
},
|
||||
labels() {
|
||||
if (!this.photo?.Labels) return [];
|
||||
if (!this.photo?.Labels) {
|
||||
return [];
|
||||
}
|
||||
// Sort by name — the backend orders by uncertainty/topicality but
|
||||
// the sidebar doesn't surface those scores.
|
||||
return this.photo.Labels.filter((l) => l.Label && l.Label.Name && l.Uncertainty < 100)
|
||||
|
|
@ -719,7 +741,9 @@ export default {
|
|||
.sort((a, b) => (a.Label.Name || "").localeCompare(b.Label.Name || "", undefined, { sensitivity: "base", numeric: true }));
|
||||
},
|
||||
albums() {
|
||||
if (!this.photo?.Albums) return [];
|
||||
if (!this.photo?.Albums) {
|
||||
return [];
|
||||
}
|
||||
return this.photo.Albums.filter((a) => a.Title && !a.Private)
|
||||
.slice()
|
||||
.sort((a, b) => (a.Title || "").localeCompare(b.Title || "", undefined, { sensitivity: "base", numeric: true }));
|
||||
|
|
@ -774,7 +798,9 @@ export default {
|
|||
label: this.$pgettext("Photo", "Title"),
|
||||
read: (p) => p?.Title,
|
||||
write: (p, v) => {
|
||||
if (p) p.Title = v;
|
||||
if (p) {
|
||||
p.Title = v;
|
||||
}
|
||||
},
|
||||
display: "text",
|
||||
maxLength: PhotoMaxLength.Title,
|
||||
|
|
@ -784,7 +810,9 @@ export default {
|
|||
label: this.$gettext("Caption"),
|
||||
read: (p) => p?.Caption,
|
||||
write: (p, v) => {
|
||||
if (p) p.Caption = v;
|
||||
if (p) {
|
||||
p.Caption = v;
|
||||
}
|
||||
},
|
||||
display: "html",
|
||||
htmlValue: "captionHtml",
|
||||
|
|
@ -796,7 +824,9 @@ export default {
|
|||
icon: "mdi-flower-tulip",
|
||||
read: (p) => p?.Details?.Subject,
|
||||
write: (p, v) => {
|
||||
if (p?.Details) p.Details.Subject = v;
|
||||
if (p?.Details) {
|
||||
p.Details.Subject = v;
|
||||
}
|
||||
},
|
||||
display: "text",
|
||||
maxLength: PhotoMaxLength.Subject,
|
||||
|
|
@ -808,7 +838,9 @@ export default {
|
|||
icon: "mdi-copyright",
|
||||
read: (p) => p?.Details?.Copyright,
|
||||
write: (p, v) => {
|
||||
if (p?.Details) p.Details.Copyright = v;
|
||||
if (p?.Details) {
|
||||
p.Details.Copyright = v;
|
||||
}
|
||||
},
|
||||
display: "text",
|
||||
maxLength: PhotoMaxLength.Copyright,
|
||||
|
|
@ -820,7 +852,9 @@ export default {
|
|||
icon: "mdi-account-tie",
|
||||
read: (p) => p?.Details?.Artist,
|
||||
write: (p, v) => {
|
||||
if (p?.Details) p.Details.Artist = v;
|
||||
if (p?.Details) {
|
||||
p.Details.Artist = v;
|
||||
}
|
||||
},
|
||||
display: "text",
|
||||
maxLength: PhotoMaxLength.Artist,
|
||||
|
|
@ -832,7 +866,9 @@ export default {
|
|||
icon: "mdi-scale-balance",
|
||||
read: (p) => p?.Details?.License,
|
||||
write: (p, v) => {
|
||||
if (p?.Details) p.Details.License = v;
|
||||
if (p?.Details) {
|
||||
p.Details.License = v;
|
||||
}
|
||||
},
|
||||
display: "text",
|
||||
maxLength: PhotoMaxLength.License,
|
||||
|
|
@ -844,7 +880,9 @@ export default {
|
|||
icon: "mdi-tag-multiple-outline",
|
||||
read: (p) => p?.Details?.Keywords,
|
||||
write: (p, v) => {
|
||||
if (p?.Details) p.Details.Keywords = v;
|
||||
if (p?.Details) {
|
||||
p.Details.Keywords = v;
|
||||
}
|
||||
},
|
||||
display: "text",
|
||||
maxLength: PhotoMaxLength.Keywords,
|
||||
|
|
@ -857,7 +895,9 @@ export default {
|
|||
icon: null,
|
||||
read: (p) => p?.Details?.Notes,
|
||||
write: (p, v) => {
|
||||
if (p?.Details) p.Details.Notes = v;
|
||||
if (p?.Details) {
|
||||
p.Details.Notes = v;
|
||||
}
|
||||
},
|
||||
display: "html",
|
||||
htmlValue: "notesHtml",
|
||||
|
|
@ -911,7 +951,9 @@ export default {
|
|||
// combined Location row. Users without places-view ACL see only the
|
||||
// lat/lng so altitude isn't leaked through the sidebar.
|
||||
coordinatesLine() {
|
||||
if (!this.model?.Lat || !this.model?.Lng) return "";
|
||||
if (!this.model?.Lat || !this.model?.Lng) {
|
||||
return "";
|
||||
}
|
||||
const coords = this.model.getLatLngShort();
|
||||
if (this.altitude && this.canViewPlaces) {
|
||||
return `${coords}\u2002${this.altitude}`;
|
||||
|
|
@ -1048,7 +1090,9 @@ export default {
|
|||
},
|
||||
},
|
||||
newMarkerUid(uid) {
|
||||
if (!uid) return;
|
||||
if (!uid) {
|
||||
return;
|
||||
}
|
||||
this.$nextTick(() => this.focusMarkerInput(uid));
|
||||
},
|
||||
},
|
||||
|
|
@ -1061,8 +1105,12 @@ export default {
|
|||
// isEditable already requires canViewLibrary so we don't repeat the
|
||||
// ACL check here; only preload the lists the user is allowed to see.
|
||||
if (this.isEditable) {
|
||||
if (this.canViewLabels) this.loadChipOptions("labels");
|
||||
if (this.canViewAlbums) this.loadChipOptions("albums");
|
||||
if (this.canViewLabels) {
|
||||
this.loadChipOptions("labels");
|
||||
}
|
||||
if (this.canViewAlbums) {
|
||||
this.loadChipOptions("albums");
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -1209,7 +1257,9 @@ export default {
|
|||
|
||||
this.$nextTick(() => {
|
||||
const editor = this._inlineEditorEl;
|
||||
if (editor && typeof editor.focus === "function") editor.focus();
|
||||
if (editor && typeof editor.focus === "function") {
|
||||
editor.focus();
|
||||
}
|
||||
});
|
||||
},
|
||||
// Eye-icon click handler (non-editable users only — display mode
|
||||
|
|
@ -1279,7 +1329,9 @@ export default {
|
|||
}
|
||||
});
|
||||
Object.keys(this.markerDrafts).forEach((uid) => {
|
||||
if (!seen.has(uid)) delete this.markerDrafts[uid];
|
||||
if (!seen.has(uid)) {
|
||||
delete this.markerDrafts[uid];
|
||||
}
|
||||
});
|
||||
},
|
||||
markerInputValue(uid) {
|
||||
|
|
@ -1312,7 +1364,9 @@ export default {
|
|||
this.$emit("naming-started");
|
||||
this.$nextTick(() => {
|
||||
const input = this.$el && this.$el.querySelector(`[data-marker-uid="${uid}"] input`);
|
||||
if (input) input.focus();
|
||||
if (input) {
|
||||
input.focus();
|
||||
}
|
||||
});
|
||||
},
|
||||
// Match a typed name against knownPeople case-insensitively so the backend
|
||||
|
|
@ -1426,7 +1480,9 @@ export default {
|
|||
this.addNameDialog = { visible: false, markerUid: "", name: "" };
|
||||
if (markerUid && name) {
|
||||
const marker = this.findMarker(markerUid);
|
||||
if (marker) this.commitMarkerName(marker, this.findKnownPerson(name), name);
|
||||
if (marker) {
|
||||
this.commitMarkerName(marker, this.findKnownPerson(name), name);
|
||||
}
|
||||
}
|
||||
this.resolveAddNameNav();
|
||||
},
|
||||
|
|
@ -1445,7 +1501,9 @@ export default {
|
|||
resolveAddNameNav() {
|
||||
const resolve = this._addNameNavResolver;
|
||||
this._addNameNavResolver = null;
|
||||
if (resolve) resolve(true);
|
||||
if (resolve) {
|
||||
resolve(true);
|
||||
}
|
||||
},
|
||||
cancelMarkerName(marker) {
|
||||
if (!marker || !marker.UID) {
|
||||
|
|
@ -1463,7 +1521,9 @@ export default {
|
|||
// marker's input (P1-9) rather than document.activeElement so an
|
||||
// unrelated focused element isn't blurred by mistake.
|
||||
const input = this.$el && this.$el.querySelector(`[data-marker-uid="${marker.UID}"] input`);
|
||||
if (input && typeof input.blur === "function") input.blur();
|
||||
if (input && typeof input.blur === "function") {
|
||||
input.blur();
|
||||
}
|
||||
},
|
||||
resetInlineEdits() {
|
||||
if (this.editingField) {
|
||||
|
|
@ -1497,7 +1557,9 @@ export default {
|
|||
hasPendingEdit() {
|
||||
for (const uid of Object.keys(this.markerDrafts)) {
|
||||
const d = this.markerDrafts[uid];
|
||||
if (!d) continue;
|
||||
if (!d) {
|
||||
continue;
|
||||
}
|
||||
if (this.unwrapMarkerName(d.current).trim() !== (d.original || "").trim()) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1531,12 +1593,18 @@ export default {
|
|||
flushDirtyMarkerDrafts() {
|
||||
Object.keys(this.markerDrafts).forEach((uid) => {
|
||||
const draft = this.markerDrafts[uid];
|
||||
if (!draft) return;
|
||||
if (!draft) {
|
||||
return;
|
||||
}
|
||||
const name = this.unwrapMarkerName(draft.current).trim();
|
||||
const original = (draft.original || "").trim();
|
||||
if (!name || name === original) return;
|
||||
if (!name || name === original) {
|
||||
return;
|
||||
}
|
||||
const marker = this.findMarker(uid);
|
||||
if (marker) this.confirmMarkerName(marker, "blur");
|
||||
if (marker) {
|
||||
this.confirmMarkerName(marker, "blur");
|
||||
}
|
||||
});
|
||||
},
|
||||
// Async guard used by the lightbox before closing / hiding / navigating.
|
||||
|
|
@ -1556,7 +1624,9 @@ export default {
|
|||
this._addNameNavResolver = resolve;
|
||||
});
|
||||
}
|
||||
if (!this.hasPendingEdit()) return Promise.resolve(true);
|
||||
if (!this.hasPendingEdit()) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
if (this.discardDialog.visible && this.discardDialog.resolver) {
|
||||
// Another request is already waiting on the dialog; reuse it.
|
||||
return new Promise((resolve) => {
|
||||
|
|
@ -1577,13 +1647,17 @@ export default {
|
|||
const r = this.discardDialog.resolver;
|
||||
this.discardDialog.resolver = null;
|
||||
this.resetInlineEdits();
|
||||
if (r) r(true);
|
||||
if (r) {
|
||||
r(true);
|
||||
}
|
||||
},
|
||||
onDiscardCancel() {
|
||||
this.discardDialog.visible = false;
|
||||
const r = this.discardDialog.resolver;
|
||||
this.discardDialog.resolver = null;
|
||||
if (r) r(false);
|
||||
if (r) {
|
||||
r(false);
|
||||
}
|
||||
},
|
||||
confirmField() {
|
||||
if (!this.photo || !this.canEdit) {
|
||||
|
|
@ -1640,7 +1714,9 @@ export default {
|
|||
if (this._editStartedAt && Date.now() - this._editStartedAt < 200) {
|
||||
return;
|
||||
}
|
||||
if (!this.editingField) return;
|
||||
if (!this.editingField) {
|
||||
return;
|
||||
}
|
||||
this.confirmField();
|
||||
},
|
||||
formatTime(model) {
|
||||
|
|
@ -2092,7 +2168,9 @@ export default {
|
|||
photo
|
||||
.update()
|
||||
.then(() => {
|
||||
if (!this.view?.model) return;
|
||||
if (!this.view?.model) {
|
||||
return;
|
||||
}
|
||||
this.view.model.TakenAtLocal = photo.TakenAtLocal;
|
||||
this.view.model.TimeZone = photo.TimeZone;
|
||||
})
|
||||
|
|
|
|||
|
|
@ -51,9 +51,15 @@ export class Album extends Collection {
|
|||
classes(selected) {
|
||||
let classes = ["is-album", "uid-" + this.UID, "type-" + this.Type];
|
||||
|
||||
if (this.Favorite) classes.push("is-favorite");
|
||||
if (this.Private) classes.push("is-private");
|
||||
if (selected) classes.push("is-selected");
|
||||
if (this.Favorite) {
|
||||
classes.push("is-favorite");
|
||||
}
|
||||
if (this.Private) {
|
||||
classes.push("is-private");
|
||||
}
|
||||
if (selected) {
|
||||
classes.push("is-selected");
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,8 +47,12 @@ export class Face extends RestModel {
|
|||
classes(selected) {
|
||||
let classes = ["is-face", "uid-" + this.ID];
|
||||
|
||||
if (this.Hidden) classes.push("is-hidden");
|
||||
if (selected) classes.push("is-selected");
|
||||
if (this.Hidden) {
|
||||
classes.push("is-hidden");
|
||||
}
|
||||
if (selected) {
|
||||
classes.push("is-selected");
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,10 +64,18 @@ export class File extends RestModel {
|
|||
classes(selected) {
|
||||
let classes = ["is-file", "uid-" + this.UID];
|
||||
|
||||
if (this.Primary) classes.push("is-primary");
|
||||
if (this.Sidecar) classes.push("is-sidecar");
|
||||
if (this.Video) classes.push("is-video");
|
||||
if (selected) classes.push("is-selected");
|
||||
if (this.Primary) {
|
||||
classes.push("is-primary");
|
||||
}
|
||||
if (this.Sidecar) {
|
||||
classes.push("is-sidecar");
|
||||
}
|
||||
if (this.Video) {
|
||||
classes.push("is-video");
|
||||
}
|
||||
if (selected) {
|
||||
classes.push("is-selected");
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,9 +38,15 @@ export class Folder extends RestModel {
|
|||
classes(selected) {
|
||||
let classes = ["is-folder", "uid-" + this.UID];
|
||||
|
||||
if (this.Favorite) classes.push("is-favorite");
|
||||
if (this.Private) classes.push("is-private");
|
||||
if (selected) classes.push("is-selected");
|
||||
if (this.Favorite) {
|
||||
classes.push("is-favorite");
|
||||
}
|
||||
if (this.Private) {
|
||||
classes.push("is-private");
|
||||
}
|
||||
if (selected) {
|
||||
classes.push("is-selected");
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,8 +35,12 @@ export class Label extends Collection {
|
|||
classes(selected) {
|
||||
let classes = ["is-label", "uid-" + this.UID];
|
||||
|
||||
if (this.Favorite) classes.push("is-favorite");
|
||||
if (selected) classes.push("is-selected");
|
||||
if (this.Favorite) {
|
||||
classes.push("is-favorite");
|
||||
}
|
||||
if (selected) {
|
||||
classes.push("is-selected");
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,9 +39,15 @@ export class Marker extends RestModel {
|
|||
classes(selected) {
|
||||
let classes = ["is-marker", "uid-" + this.getId()];
|
||||
|
||||
if (this.Invalid) classes.push("is-invalid");
|
||||
if (this.Review) classes.push("is-review");
|
||||
if (selected) classes.push("is-selected");
|
||||
if (this.Invalid) {
|
||||
classes.push("is-invalid");
|
||||
}
|
||||
if (this.Review) {
|
||||
classes.push("is-review");
|
||||
}
|
||||
if (selected) {
|
||||
classes.push("is-selected");
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,9 @@ export class Model {
|
|||
// (used when a partial update should not reset object diffs). The reserved
|
||||
// key "__originalValues" is always ignored. No-op for falsy `values`.
|
||||
setValues(values, scalarOnly) {
|
||||
if (!values) return;
|
||||
if (!values) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let key in values) {
|
||||
if (values.hasOwnProperty(key) && key !== "__originalValues") {
|
||||
|
|
|
|||
|
|
@ -158,12 +158,24 @@ export class Photo extends RestModel {
|
|||
generateClasses = memoizeOne((isPlayable, isInClipboard, portrait, favorite, isPrivate, isStack) => {
|
||||
let classes = ["is-photo", "uid-" + this.UID, "type-" + this.Type];
|
||||
|
||||
if (isPlayable) classes.push("is-playable");
|
||||
if (isInClipboard) classes.push("is-selected");
|
||||
if (portrait) classes.push("is-portrait");
|
||||
if (favorite) classes.push("is-favorite");
|
||||
if (isPrivate) classes.push("is-private");
|
||||
if (isStack) classes.push("is-stack");
|
||||
if (isPlayable) {
|
||||
classes.push("is-playable");
|
||||
}
|
||||
if (isInClipboard) {
|
||||
classes.push("is-selected");
|
||||
}
|
||||
if (portrait) {
|
||||
classes.push("is-portrait");
|
||||
}
|
||||
if (favorite) {
|
||||
classes.push("is-favorite");
|
||||
}
|
||||
if (isPrivate) {
|
||||
classes.push("is-private");
|
||||
}
|
||||
if (isStack) {
|
||||
classes.push("is-stack");
|
||||
}
|
||||
|
||||
return classes;
|
||||
});
|
||||
|
|
@ -762,27 +774,35 @@ export class Photo extends RestModel {
|
|||
// Originals only?
|
||||
if (s.download.originals && file.Root.length > 1) {
|
||||
// Don't download broken files and sidecars.
|
||||
if ($config.debug) console.log(`download: skipped ${file.Root} file ${file.Name}`);
|
||||
if ($config.debug) {
|
||||
console.log(`download: skipped ${file.Root} file ${file.Name}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip metadata sidecar files?
|
||||
if (!s.download.mediaSidecar && (file.MediaType === media.Sidecar || file.Sidecar)) {
|
||||
// Don't download broken files and sidecars.
|
||||
if ($config.debug) console.log(`download: skipped sidecar file ${file.Name}`);
|
||||
if ($config.debug) {
|
||||
console.log(`download: skipped sidecar file ${file.Name}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip RAW images?
|
||||
if (!s.download.mediaRaw && (file.MediaType === media.Raw || file.FileType === media.Raw)) {
|
||||
if ($config.debug) console.log(`download: skipped raw file ${file.Name}`);
|
||||
if ($config.debug) {
|
||||
console.log(`download: skipped raw file ${file.Name}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is a video, always skip stacked images...
|
||||
// see https://github.com/photoprism/photoprism/issues/1436
|
||||
if (this.Type === media.Video && !(file.MediaType === media.Video || file.Video)) {
|
||||
if ($config.debug) console.log(`download: skipped video sidecar ${file.Name}`);
|
||||
if ($config.debug) {
|
||||
console.log(`download: skipped video sidecar ${file.Name}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1102,10 +1122,18 @@ export class Photo extends RestModel {
|
|||
|
||||
getExifInfo() {
|
||||
const parts = [];
|
||||
if (this.FocalLength) parts.push(this.FocalLength + "mm");
|
||||
if (this.FNumber) parts.push("\u0192/" + this.FNumber);
|
||||
if (this.Iso) parts.push("ISO " + this.Iso);
|
||||
if (this.Exposure) parts.push(this.Exposure);
|
||||
if (this.FocalLength) {
|
||||
parts.push(this.FocalLength + "mm");
|
||||
}
|
||||
if (this.FNumber) {
|
||||
parts.push("\u0192/" + this.FNumber);
|
||||
}
|
||||
if (this.Iso) {
|
||||
parts.push("ISO " + this.Iso);
|
||||
}
|
||||
if (this.Exposure) {
|
||||
parts.push(this.Exposure);
|
||||
}
|
||||
return parts.join(" \u2022 ");
|
||||
}
|
||||
|
||||
|
|
@ -1210,7 +1238,9 @@ export class Photo extends RestModel {
|
|||
// Distinct from Thumb.addToAlbum (grid layer, Removed flag); both contracts
|
||||
// are pinned in tests.
|
||||
addToAlbum(albumUID) {
|
||||
if (!albumUID) return Promise.resolve(this);
|
||||
if (!albumUID) {
|
||||
return Promise.resolve(this);
|
||||
}
|
||||
return $api
|
||||
.post(`albums/${albumUID}/photos`, { photos: [this.UID] })
|
||||
.then(() => {
|
||||
|
|
@ -1222,7 +1252,9 @@ export class Photo extends RestModel {
|
|||
|
||||
// removeFromAlbum mirrors addToAlbum's evict + refind pattern.
|
||||
removeFromAlbum(albumUID) {
|
||||
if (!albumUID) return Promise.resolve(this);
|
||||
if (!albumUID) {
|
||||
return Promise.resolve(this);
|
||||
}
|
||||
return $api
|
||||
.delete(`albums/${albumUID}/photos`, { data: { photos: [this.UID] } })
|
||||
.then(() => {
|
||||
|
|
@ -1380,7 +1412,9 @@ export class Photo extends RestModel {
|
|||
const start = Math.max(0, index - before);
|
||||
const end = Math.min(models.length - 1, index + after);
|
||||
for (let i = start; i <= end; i++) {
|
||||
if (i === index) continue;
|
||||
if (i === index) {
|
||||
continue;
|
||||
}
|
||||
const uid = models[i]?.UID;
|
||||
if (uid) {
|
||||
tasks.push(Photo.prefetch(uid));
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ export class Settings extends Model {
|
|||
}
|
||||
|
||||
setValues(values, scalarOnly) {
|
||||
if (!values) return;
|
||||
if (!values) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (values.maps?.style === "basic" || values.maps?.style === "offline") {
|
||||
values.maps.style = "";
|
||||
|
|
|
|||
|
|
@ -46,11 +46,21 @@ export class Subject extends Collection {
|
|||
classes(selected) {
|
||||
let classes = ["is-subject", "uid-" + this.UID];
|
||||
|
||||
if (this.Favorite) classes.push("is-favorite");
|
||||
if (this.Hidden) classes.push("is-hidden");
|
||||
if (this.Private) classes.push("is-private");
|
||||
if (this.Excluded) classes.push("is-excluded");
|
||||
if (selected) classes.push("is-selected");
|
||||
if (this.Favorite) {
|
||||
classes.push("is-favorite");
|
||||
}
|
||||
if (this.Hidden) {
|
||||
classes.push("is-hidden");
|
||||
}
|
||||
if (this.Private) {
|
||||
classes.push("is-private");
|
||||
}
|
||||
if (this.Excluded) {
|
||||
classes.push("is-excluded");
|
||||
}
|
||||
if (selected) {
|
||||
classes.push("is-selected");
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -310,7 +310,9 @@ export default {
|
|||
this.loadMore();
|
||||
},
|
||||
loadMore() {
|
||||
if (this.scrollDisabled) return;
|
||||
if (this.scrollDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.offset === 0) {
|
||||
this.loading = true;
|
||||
|
|
|
|||
|
|
@ -569,7 +569,9 @@ export default {
|
|||
});
|
||||
},
|
||||
onShow(model) {
|
||||
if (this.busy || !model) return;
|
||||
if (this.busy || !model) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.busy = true;
|
||||
model.show().finally(() => {
|
||||
|
|
@ -578,7 +580,9 @@ export default {
|
|||
});
|
||||
},
|
||||
onHide(model) {
|
||||
if (this.busy || !model) return;
|
||||
if (this.busy || !model) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.busy = true;
|
||||
model.hide().finally(() => {
|
||||
|
|
@ -587,7 +591,9 @@ export default {
|
|||
});
|
||||
},
|
||||
toggleHidden(model) {
|
||||
if (this.busy || !model) return;
|
||||
if (this.busy || !model) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.busy = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -790,7 +790,9 @@ export default {
|
|||
this.dirty = true;
|
||||
this.complete = false;
|
||||
|
||||
if (this.context !== contexts.Archive) break;
|
||||
if (this.context !== contexts.Archive) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (let i = 0; i < data.entities.length; i++) {
|
||||
const uid = data.entities[i];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue