mirror of
https://github.com/photoprism/photoprism.git
synced 2026-07-17 16:49:04 +00:00
* Frontend: Add settings for albums, favorites, folders, and media features * Frontend: Enhance download settings for files and albums #848 * Frontend: Enhance download settings for files and albums #848 * Frontend: Update album download settings to restrict access to super admins * Frontend: Add album sorting options to settings for super admins * Frontend: Update download settings visibility * Frontend: Refine download settings access control for user roles * Tests: Add album, folder, moment, state, and month order selection options to page-model * Frontend: Update access control for super admins in settings to include scope check * Tests: Add new tests for download name and album order settings persistence * Tests: Implement new tests for default album sort order persistence in settings * Frontend: Move default album order settings * Frontend: Add zoom accessibility option in settings #799 * Frontend: Update download name from "Share Identifier" to "Share Friendly" and refine hints for download options in settings * Tests: Add new acceptance test for disabling album downloads while allowing file downloads in settings * Frontend: Improve download options layout and hints * Frontend: Refactor download options condition in settings page * Frontend: Implement native zoom control in lightbox component * Settings: Add Collections tab and move Services to its own page #848 #5429 - Add a super-admin Collections tab after Content holding the album Download and Sort Order settings; remove those cards from Content. - Move Services from a settings tab to a dedicated page (page/services.vue) with a search toolbar and a Reload / Learn More action menu; link it from the navigation settings menu. - Implement server-side services search (acc_name filter) with tests. - De-duplicate the picture sort-order options into a shared SortOrderOptions builder in options.js. - Update acceptance/unit tests and page models to match. * Frontend: Regenerate translation catalogs #848 #5429 Ran `make gettext-extract` to pick up the new settings strings (Collections, Features, Disable Downloads, and related messages) so Weblate can translate them. Existing translations are preserved. * Frontend: Move lightbox viewport-zoom handling into the view helper - Move the viewport pinch-zoom lock/restore (disableNativeZoom / restoreNativeZoom + savedViewportContent) from lightbox.vue onto the View singleton in common/view.js. - Drive it passively from View.apply() via a disableViewportZoom flag in the PLightbox case, alongside hideScrollbar / disableScrolling / disableNavigationGestures; lightbox.vue no longer references zoom. - Make save/restore idempotent and extract the viewport constants. - Cover the helpers and the apply() wiring in view.test.js. * Settings: Add Accessibility section to General with Reduce Motion #848 #5429 - Move the page-zoom checkbox out of the feature-flags grid into a dedicated Accessibility card (super-admin only) with four toggles: Open on Hover, Reduce Motion, Hide Scrollbar, and Allow Page Zoom. - Add a UI.ReduceMotion flag (defaults false) that toggles an html.reduce-motion class via config.setReduceMotion and suppresses map fly-to animations non-destructively (util.mapAnimateDuration). - Make Open on Hover apply live by reading shouldOpenOnHover() through a reactive computed in the action/auth/lightbox menus; Zoom and Hide Scrollbar reload since they are baked into the server-rendered HTML. - Reword two feature-grid hints to the "pictures" convention and cover the new helpers with Go and Vitest tests. * Frontend: Regenerate translation catalogs #848 #5429 Ran `make gettext-extract` to pick up the new Accessibility settings strings (Accessibility, Allow Page Zoom, Open on Hover, Hide Scrollbar, Reduce Motion, and their hints) and the reworded feature-grid hints. Existing translations are preserved. --------- Co-authored-by: Michael Mayer <michael@photoprism.app>
122 lines
4.4 KiB
Vue
122 lines
4.4 KiB
Vue
<template>
|
|
<v-menu
|
|
v-model="open"
|
|
location="top"
|
|
offset="6"
|
|
:min-width="250"
|
|
:z-index="2500"
|
|
:open-on-hover="openOnHover"
|
|
class="p-action-menu action-menu nav-user-menu"
|
|
content-class="nav-user-menu__content"
|
|
@update:model-value="onToggle"
|
|
>
|
|
<template #activator="{ props }">
|
|
<div v-bind="props" class="nav-user-activator clickable" :title="$gettext('Account')">
|
|
<slot></slot>
|
|
</div>
|
|
</template>
|
|
<v-list slim nav density="compact" bg-color="navigation" class="action-menu__list">
|
|
<template v-if="instances.length > 0">
|
|
<v-list-item
|
|
v-for="instance in instances"
|
|
:key="instance.namespace"
|
|
:title="instance.title"
|
|
:subtitle="instance.path || undefined"
|
|
:nav="true"
|
|
class="action-menu__item"
|
|
@click="onSwitch(instance)"
|
|
>
|
|
<template #prepend>
|
|
<img :src="instance.icon" :alt="instance.title" class="nav-user-menu__icon" @error="onIconError(instance)" />
|
|
</template>
|
|
</v-list-item>
|
|
<v-divider class="my-1"></v-divider>
|
|
</template>
|
|
<v-list-item
|
|
v-if="$config.feature('account')"
|
|
prepend-icon="mdi-shield-account-variant"
|
|
:title="$gettext('Manage Account')"
|
|
class="action-menu__item action-account"
|
|
@click="$emit('account')"
|
|
></v-list-item>
|
|
<v-list-item prepend-icon="mdi-power" :title="$gettext('Sign Out')" class="action-menu__item action-logout" @click="$emit('logout')"></v-list-item>
|
|
</v-list>
|
|
</v-menu>
|
|
</template>
|
|
|
|
<script>
|
|
import { listReachableInstances, instancePath } from "common/instances";
|
|
|
|
// defaultIcon is shown when a peer has no recorded app icon, or its icon fails to
|
|
// load; it is the bundled PhotoPrism logo served at the origin root.
|
|
const defaultIcon = "/static/icons/logo.svg";
|
|
|
|
// PAuthMenu is the navigation avatar overlay menu: Manage Account, Sign Out, and —
|
|
// when more than one instance is reachable on a shared domain — a Switch Instance
|
|
// list. It emits "account" and "logout" for the host navigation to handle.
|
|
export default {
|
|
name: "PAuthMenu",
|
|
emits: ["logout", "account"],
|
|
data() {
|
|
return {
|
|
open: false,
|
|
instances: [],
|
|
};
|
|
},
|
|
computed: {
|
|
// openOnHover reactively reflects the user's menu open-on-hover preference so a settings
|
|
// change applies without a page reload (persistent menus keep no mount-time snapshot).
|
|
openOnHover() {
|
|
return this.$util.shouldOpenOnHover();
|
|
},
|
|
},
|
|
mounted() {
|
|
this.refresh();
|
|
},
|
|
methods: {
|
|
// refresh rescans shared localStorage for reachable peer instances, adding a
|
|
// display path (the SiteUrl's base path) for the menu subtitle.
|
|
refresh() {
|
|
const namespace = this.$config?.values?.storageNamespace;
|
|
this.instances = listReachableInstances({ currentNamespace: namespace }).map((instance) => {
|
|
// Only proxied instances (e.g. "/i/pro-1") get a path subtitle; a root
|
|
// ("/") or empty path means the Portal itself, so leave it blank.
|
|
const path = instancePath(instance.url);
|
|
return {
|
|
...instance,
|
|
path: path && path !== "/" ? path : "",
|
|
icon: instance.icon || defaultIcon,
|
|
};
|
|
});
|
|
},
|
|
// onToggle rescans peers each time the menu opens so the switcher stays current.
|
|
onToggle(open) {
|
|
if (open) {
|
|
this.refresh();
|
|
}
|
|
},
|
|
// onSwitch navigates the browser to the selected instance's app entry point
|
|
// (its frontend route, e.g. /i/pro-1/library or /portal) so the user lands
|
|
// in the app instead of a web-overlay landing page; its own login then completes
|
|
// the standard OIDC flow when a session is required. Falls back to the SiteUrl.
|
|
onSwitch(instance) {
|
|
const target = instance && (instance.route || instance.url);
|
|
if (target) {
|
|
this.navigate(target);
|
|
}
|
|
},
|
|
// navigate sends the browser to the given URL (a seam so onSwitch is testable).
|
|
navigate(url) {
|
|
window.location.assign(url);
|
|
},
|
|
// onIconError falls back to the default logo when an instance's recorded app
|
|
// icon fails to load (e.g. a removed asset or one served cross-origin). The
|
|
// guard avoids an error loop if the default logo itself fails.
|
|
onIconError(instance) {
|
|
if (instance.icon !== defaultIcon) {
|
|
instance.icon = defaultIcon;
|
|
}
|
|
},
|
|
},
|
|
};
|
|
</script>
|