big: remove initial state except for shares

Signed-off-by: Varun Patil <radialapps@gmail.com>
pull/653/head
Varun Patil 2023-05-02 22:52:00 -07:00
parent 80836d4e05
commit 37abc991b1
26 changed files with 330 additions and 206 deletions

View File

@ -84,6 +84,7 @@ return [
// Config // Config
['name' => 'Other#setUserConfig', 'url' => '/api/config/{key}', 'verb' => 'PUT'], ['name' => 'Other#setUserConfig', 'url' => '/api/config/{key}', 'verb' => 'PUT'],
['name' => 'Other#getUserConfig', 'url' => '/api/config', 'verb' => 'GET'],
// Admin // Admin
['name' => 'Admin#getSystemStatus', 'url' => '/api/system-status', 'verb' => 'GET'], ['name' => 'Admin#getSystemStatus', 'url' => '/api/system-status', 'verb' => 'GET'],

View File

@ -56,6 +56,50 @@ class OtherController extends GenericApiController
}); });
} }
/**
* @NoAdminRequired
*
* @NoCSRFRequired
*
* @PublicPage
*/
public function getUserConfig(): Http\Response
{
return Util::guardEx(function () {
$appManager = \OC::$server->get(\OCP\App\IAppManager::class);
try {
$uid = Util::getUID();
} catch (\Exception $e) {
$uid = null;
}
$getAppConfig = function ($key, $default) use ($uid) {
return $this->config->getUserValue($uid, Application::APPNAME, $key, $default);
};
return new JSONResponse([
"version" => $appManager->getAppInfo('memories')['version'],
"vod_disable" => Util::getSystemConfig('memories.vod.disable'),
"video_default_quality" => Util::getSystemConfig('memories.video_default_quality'),
"places_gis" => Util::getSystemConfig('memories.gis_type'),
"systemtags_enabled" => Util::tagsIsEnabled(),
"recognize_enabled" => Util::recognizeIsEnabled(),
"albums_enabled" => Util::albumsIsEnabled(),
"facerecognition_installed" => Util::facerecognitionIsInstalled(),
"facerecognition_enabled" => Util::facerecognitionIsEnabled(),
"timeline_path" => $getAppConfig('timelinePath', 'EMPTY'),
"folders_path" => $getAppConfig('foldersPath', '/'),
"show_hidden_folders" => $getAppConfig('showHidden', false) === "true",
"sort_folder_month" => $getAppConfig('sortFolderMonth', false) === "true",
"sort_album_month" => $getAppConfig('sortAlbumMonth', "true") === "true",
"enable_top_memories" => $getAppConfig('enableTopMemories', 'true') === "true",
], Http::STATUS_OK);
});
}
/** /**
* @NoAdminRequired * @NoAdminRequired
* *

View File

@ -58,35 +58,6 @@ class PageController extends Controller
OCPUtil::addScript($this->appName, 'memories-main'); OCPUtil::addScript($this->appName, 'memories-main');
$this->eventDispatcher->dispatchTyped(new LoadSidebar()); $this->eventDispatcher->dispatchTyped(new LoadSidebar());
// Configuration
$uid = $user->getUID();
$pi = function ($key, $default) use ($uid) {
$this->initialState->provideInitialState($key, $this->config->getUserValue(
$uid,
Application::APPNAME,
$key,
$default
));
};
// User configuration
$pi('timelinePath', 'EMPTY');
$pi('foldersPath', '/');
$pi('showHidden', false);
$pi('sortFolderMonth', false);
$pi('sortAlbumMonth', 'true');
$pi('enableTopMemories', 'true');
// Apps enabled
$this->initialState->provideInitialState('systemtags', Util::tagsIsEnabled());
$this->initialState->provideInitialState('recognize', Util::recognizeIsEnabled());
$this->initialState->provideInitialState('facerecognitionInstalled', Util::facerecognitionIsInstalled());
$this->initialState->provideInitialState('facerecognitionEnabled', Util::facerecognitionIsEnabled());
$this->initialState->provideInitialState('albums', Util::albumsIsEnabled());
// Common state
self::provideCommonInitialState($this->initialState);
// Extra translations // Extra translations
if (Util::recognizeIsEnabled()) { if (Util::recognizeIsEnabled()) {
// Auto translation for tags // Auto translation for tags
@ -138,22 +109,6 @@ class PageController extends Controller
return $policy; return $policy;
} }
/** Provide initial state for all pages */
public static function provideCommonInitialState(IInitialState &$initialState)
{
$appManager = \OC::$server->get(\OCP\App\IAppManager::class);
// App version
$initialState->provideInitialState('version', $appManager->getAppInfo('memories')['version']);
// Video configuration
$initialState->provideInitialState('vod_disable', Util::getSystemConfig('memories.vod.disable'));
$initialState->provideInitialState('video_default_quality', Util::getSystemConfig('memories.video_default_quality'));
// Geo configuration
$initialState->provideInitialState('places_gis', Util::getSystemConfig('memories.gis_type'));
}
/** /**
* @NoAdminRequired * @NoAdminRequired
* *

View File

@ -85,7 +85,6 @@ class PublicAlbumController extends Controller
// Scripts // Scripts
Util::addScript($this->appName, 'memories-main'); Util::addScript($this->appName, 'memories-main');
PageController::provideCommonInitialState($this->initialState);
$response = new PublicTemplateResponse($this->appName, 'main'); $response = new PublicTemplateResponse($this->appName, 'main');
$response->setHeaderTitle($album['name']); $response->setHeaderTitle($album['name']);

View File

@ -111,7 +111,6 @@ class PublicController extends AuthPublicShareController
// Scripts // Scripts
\OCP\Util::addScript($this->appName, 'memories-main'); \OCP\Util::addScript($this->appName, 'memories-main');
PageController::provideCommonInitialState($this->initialState);
// Share info // Share info
$this->initialState->provideInitialState('no_download', $share->getHideDownload()); $this->initialState->provideInitialState('no_download', $share->getHideDownload());

View File

@ -59,7 +59,7 @@ const NcAppNavigationItem = () => import('@nextcloud/vue/dist/Components/NcAppNa
import { generateUrl } from '@nextcloud/router'; import { generateUrl } from '@nextcloud/router';
import { translate as t } from '@nextcloud/l10n'; import { translate as t } from '@nextcloud/l10n';
import { emit } from '@nextcloud/event-bus'; import { emit, subscribe } from '@nextcloud/event-bus';
import * as utils from './services/Utils'; import * as utils from './services/Utils';
import UserConfig from './mixins/UserConfig'; import UserConfig from './mixins/UserConfig';
@ -137,11 +137,11 @@ export default defineComponent({
}, },
recognize(): string | false { recognize(): string | false {
if (!this.config_recognizeEnabled) { if (!this.config.recognize_enabled) {
return false; return false;
} }
if (this.config_facerecognitionInstalled) { if (this.config.facerecognition_enabled) {
return t('memories', 'People (Recognize)'); return t('memories', 'People (Recognize)');
} }
@ -149,11 +149,11 @@ export default defineComponent({
}, },
facerecognition(): string | false { facerecognition(): string | false {
if (!this.config_facerecognitionInstalled) { if (!this.config.facerecognition_installed) {
return false; return false;
} }
if (this.config_recognizeEnabled) { if (this.config.recognize_enabled) {
return t('memories', 'People (Face Recognition)'); return t('memories', 'People (Face Recognition)');
} }
@ -161,11 +161,15 @@ export default defineComponent({
}, },
isFirstStart(): boolean { isFirstStart(): boolean {
return this.config_timelinePath === 'EMPTY'; return (
this.config.timeline_path === 'EMPTY' &&
this.$route.name !== 'folder-share' &&
this.$route.name !== 'album-share'
);
}, },
showAlbums(): boolean { showAlbums(): boolean {
return this.config_albumsEnabled; return this.config.albums_enabled;
}, },
removeOuterGap(): boolean { removeOuterGap(): boolean {
@ -197,13 +201,14 @@ export default defineComponent({
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
utils.setRenewingTimeout(this, 'resizeTimer', onResize, 100); utils.setRenewingTimeout(this, 'resizeTimer', onResize, 100);
}); });
// Register navigation items on config change
subscribe(this.configEventName, this.refreshNav);
}, },
mounted() { mounted() {
this.doRouteChecks(); this.doRouteChecks();
this.refreshNav();
// Populate navigation
this.navItems = this.navItemsAll().filter((item) => typeof item.if === 'undefined' || Boolean(item.if));
// Store CSS variables modified // Store CSS variables modified
const root = document.documentElement; const root = document.documentElement;
@ -255,8 +260,8 @@ export default defineComponent({
}, },
methods: { methods: {
navItemsAll(): NavItem[] { refreshNav() {
return [ const navItems = [
{ {
name: 'timeline', name: 'timeline',
icon: ImageMultiple, icon: ImageMultiple,
@ -309,7 +314,7 @@ export default defineComponent({
name: 'places', name: 'places',
icon: MarkerIcon, icon: MarkerIcon,
title: t('memories', 'Places'), title: t('memories', 'Places'),
if: this.config_placesGis > 0, if: this.config.places_gis > 0,
}, },
{ {
name: 'map', name: 'map',
@ -320,9 +325,11 @@ export default defineComponent({
name: 'tags', name: 'tags',
icon: TagsIcon, icon: TagsIcon,
title: t('memories', 'Tags'), title: t('memories', 'Tags'),
if: this.config_tagsEnabled, if: this.config.systemtags_enabled,
}, },
]; ];
this.navItems = navItems.filter((item) => typeof item.if === 'undefined' || Boolean(item.if));
}, },
linkClick() { linkClick() {

View File

@ -53,11 +53,11 @@ export default defineComponent({
}, },
created() { created() {
subscribe(this.config_eventName, this.routeChange); subscribe(this.configEventName, this.routeChange);
}, },
beforeDestroy() { beforeDestroy() {
unsubscribe(this.config_eventName, this.routeChange); unsubscribe(this.configEventName, this.routeChange);
}, },
watch: { watch: {
@ -74,7 +74,7 @@ export default defineComponent({
this.loading++; this.loading++;
if (route === 'albums') { if (route === 'albums') {
this.items = await dav.getAlbums(3, this.config_albumListSort); this.items = await dav.getAlbums(3, this.config.album_list_sort);
} else if (route === 'tags') { } else if (route === 'tags') {
this.items = await dav.getTags(); this.items = await dav.getTags();
} else if (route === 'recognize' || route === 'facerecognition') { } else if (route === 'recognize' || route === 'facerecognition') {

View File

@ -118,8 +118,8 @@ export default defineComponent({
async finish() { async finish() {
this.show = false; this.show = false;
await new Promise((resolve) => setTimeout(resolve, 500)); await new Promise((resolve) => setTimeout(resolve, 500));
this.config_timelinePath = this.chosenPath; this.config.timeline_path = this.chosenPath;
await this.updateSetting('timelinePath'); await this.updateSetting('timeline_path', 'timelinePath');
}, },
async chooseFolder(title: string, initial: string) { async chooseFolder(title: string, initial: string) {

View File

@ -254,7 +254,7 @@ export default defineComponent({
/** Is album route */ /** Is album route */
routeIsAlbum() { routeIsAlbum() {
return this.config_albumsEnabled && this.$route.name === 'albums'; return this.config.albums_enabled && this.$route.name === 'albums';
}, },
/** Public route that can't modify anything */ /** Public route that can't modify anything */
@ -786,7 +786,7 @@ export default defineComponent({
* Move selected photos to another person * Move selected photos to another person
*/ */
async moveSelectionToPerson(selection: Selection) { async moveSelectionToPerson(selection: Selection) {
if (!this.config_showFaceRect) { if (!this.config.show_face_rect) {
showError(this.t('memories', 'You must enable "Mark person in preview" to use this feature')); showError(this.t('memories', 'You must enable "Mark person in preview" to use this feature'));
return; return;
} }

View File

@ -30,39 +30,51 @@
> >
<NcAppSettingsSection id="general-settings" :title="t('memories', 'General')"> <NcAppSettingsSection id="general-settings" :title="t('memories', 'General')">
<label for="timeline-path">{{ t('memories', 'Timeline Path') }}</label> <label for="timeline-path">{{ t('memories', 'Timeline Path') }}</label>
<input id="timeline-path" @click="chooseTimelinePath" v-model="config_timelinePath" type="text" /> <input id="timeline-path" @click="chooseTimelinePath" v-model="config.timeline_path" type="text" />
<NcCheckboxRadioSwitch :checked.sync="config_squareThumbs" @update:checked="updateSquareThumbs" type="switch"> <NcCheckboxRadioSwitch :checked.sync="config.square_thumbs" @update:checked="updateSquareThumbs" type="switch">
{{ t('memories', 'Square grid mode') }} {{ t('memories', 'Square grid mode') }}
</NcCheckboxRadioSwitch> </NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch <NcCheckboxRadioSwitch
:checked.sync="config_enableTopMemories" :checked.sync="config.enable_top_memories"
@update:checked="updateEnableTopMemories" @update:checked="updateEnableTopMemories"
type="switch" type="switch"
> >
{{ t('memories', 'Show past photos on top of timeline') }} {{ t('memories', 'Show past photos on top of timeline') }}
</NcCheckboxRadioSwitch> </NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch :checked.sync="config_fullResOnZoom" @update:checked="updateFullResOnZoom" type="switch"> <NcCheckboxRadioSwitch
:checked.sync="config.full_res_on_zoom"
@update:checked="updateFullResOnZoom"
type="switch"
>
{{ t('memories', 'Load full size image on zoom') }} {{ t('memories', 'Load full size image on zoom') }}
</NcCheckboxRadioSwitch> </NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch :checked.sync="config_fullResAlways" @update:checked="updateFullResAlways" type="switch"> <NcCheckboxRadioSwitch
:checked.sync="config.full_res_always"
@update:checked="updateFullResAlways"
type="switch"
>
{{ t('memories', 'Always load full size image (not recommended)') }} {{ t('memories', 'Always load full size image (not recommended)') }}
</NcCheckboxRadioSwitch> </NcCheckboxRadioSwitch>
</NcAppSettingsSection> </NcAppSettingsSection>
<NcAppSettingsSection id="folders-settings" :title="t('memories', 'Folders')"> <NcAppSettingsSection id="folders-settings" :title="t('memories', 'Folders')">
<label for="folders-path">{{ t('memories', 'Folders Path') }}</label> <label for="folders-path">{{ t('memories', 'Folders Path') }}</label>
<input id="folders-path" @click="chooseFoldersPath" v-model="config_foldersPath" type="text" /> <input id="folders-path" @click="chooseFoldersPath" v-model="config.folders_path" type="text" />
<NcCheckboxRadioSwitch :checked.sync="config_showHidden" @update:checked="updateShowHidden" type="switch"> <NcCheckboxRadioSwitch
:checked.sync="config.show_hidden_folders"
@update:checked="updateShowHidden"
type="switch"
>
{{ t('memories', 'Show hidden folders') }} {{ t('memories', 'Show hidden folders') }}
</NcCheckboxRadioSwitch> </NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch <NcCheckboxRadioSwitch
:checked.sync="config_sortFolderMonth" :checked.sync="config.sort_folder_month"
@update:checked="updateSortFolderMonth" @update:checked="updateSortFolderMonth"
type="switch" type="switch"
> >
@ -72,7 +84,7 @@
<NcAppSettingsSection id="albums-settings" :title="t('memories', 'Albums')"> <NcAppSettingsSection id="albums-settings" :title="t('memories', 'Albums')">
<NcCheckboxRadioSwitch <NcCheckboxRadioSwitch
:checked.sync="config_sortAlbumMonth" :checked.sync="config.sort_album_month"
@update:checked="updateSortAlbumMonth" @update:checked="updateSortAlbumMonth"
type="switch" type="switch"
> >
@ -147,57 +159,57 @@ export default defineComponent({
}, },
async chooseTimelinePath() { async chooseTimelinePath() {
(<any>this.$refs.multiPathModal).open(this.config_timelinePath.split(';')); (<any>this.$refs.multiPathModal).open(this.config.timeline_path.split(';'));
}, },
async saveTimelinePath(paths: string[]) { async saveTimelinePath(paths: string[]) {
if (!paths || !paths.length) return; if (!paths || !paths.length) return;
const newPath = paths.join(';'); const newPath = paths.join(';');
if (newPath !== this.config_timelinePath) { if (newPath !== this.config.timeline_path) {
this.config_timelinePath = newPath; this.config.timeline_path = newPath;
await this.updateSetting('timelinePath'); await this.updateSetting('timeline_path', 'timelinePath');
} }
}, },
async chooseFoldersPath() { async chooseFoldersPath() {
let newPath = await this.chooseFolder( let newPath = await this.chooseFolder(
this.t('memories', 'Choose the root for the folders view'), this.t('memories', 'Choose the root for the folders view'),
this.config_foldersPath this.config.folders_path
); );
if (newPath === '') newPath = '/'; if (newPath === '') newPath = '/';
if (newPath !== this.config_foldersPath) { if (newPath !== this.config.folders_path) {
this.config_foldersPath = newPath; this.config.folders_path = newPath;
await this.updateSetting('foldersPath'); await this.updateSetting('folders_path', 'foldersPath');
} }
}, },
async updateSquareThumbs() { async updateSquareThumbs() {
await this.updateSetting('squareThumbs'); await this.updateSetting('square_thumbs');
}, },
async updateFullResOnZoom() { async updateFullResOnZoom() {
await this.updateSetting('fullResOnZoom'); await this.updateSetting('full_res_on_zoom');
}, },
async updateFullResAlways() { async updateFullResAlways() {
await this.updateSetting('fullResAlways'); await this.updateSetting('full_res_always');
}, },
async updateEnableTopMemories() { async updateEnableTopMemories() {
await this.updateSetting('enableTopMemories'); await this.updateSetting('enable_top_memories', 'enableTopMemories');
}, },
async updateShowHidden() { async updateShowHidden() {
await this.updateSetting('showHidden'); await this.updateSetting('show_hidden_folders', 'showHidden');
}, },
async updateSortFolderMonth() { async updateSortFolderMonth() {
await this.updateSetting('sortFolderMonth'); await this.updateSetting('sort_folder_month', 'sortFolderMonth');
}, },
async updateSortAlbumMonth() { async updateSortAlbumMonth() {
await this.updateSetting('sortAlbumMonth'); await this.updateSetting('sort_album_month', 'sortAlbumMonth');
}, },
}, },
}); });

View File

@ -30,8 +30,8 @@
</div> </div>
<OnThisDay <OnThisDay
v-if="routeIsBase && config_enableTopMemories" v-if="routeIsBase && config.enable_top_memories"
:key="config_timelinePath" :key="config.timeline_path"
:viewer="$refs.viewer" :viewer="$refs.viewer"
@load="scrollerManager.adjust()" @load="scrollerManager.adjust()"
> >
@ -204,16 +204,17 @@ export default defineComponent({
}, },
created() { created() {
subscribe(this.config_eventName, this.softRefresh); subscribe(this.configEventName, this.softRefresh);
subscribe('files:file:created', this.softRefresh); subscribe('files:file:created', this.softRefresh);
subscribe('memories:window:resize', this.handleResizeWithDelay); subscribe('memories:window:resize', this.handleResizeWithDelay);
}, },
beforeDestroy() { beforeDestroy() {
unsubscribe(this.config_eventName, this.softRefresh); unsubscribe(this.configEventName, this.softRefresh);
unsubscribe('files:file:created', this.softRefresh); unsubscribe('files:file:created', this.softRefresh);
unsubscribe('memories:window:resize', this.handleResizeWithDelay); unsubscribe('memories:window:resize', this.handleResizeWithDelay);
this.resetState(); this.resetState();
this.state = 0;
}, },
computed: { computed: {
@ -234,8 +235,8 @@ export default defineComponent({
return ( return (
this.$route.query.sort === 'album' || this.$route.query.sort === 'album' ||
(this.config_sortAlbumMonth && (this.$route.name === 'albums' || this.$route.name === 'album-share')) || (this.config.sort_album_month && (this.$route.name === 'albums' || this.$route.name === 'album-share')) ||
(this.config_sortFolderMonth && this.$route.name === 'folders') (this.config.sort_folder_month && this.$route.name === 'folders')
); );
}, },
@ -316,7 +317,7 @@ export default defineComponent({
}, },
allowBreakout() { allowBreakout() {
return globalThis.windowInnerWidth <= 600 && !this.config_squareThumbs; return globalThis.windowInnerWidth <= 600 && !this.config.square_thumbs;
}, },
/** Create new state */ /** Create new state */
@ -412,7 +413,7 @@ export default defineComponent({
this.rowHeight = Math.floor(this.rowWidth / this.numCols); this.rowHeight = Math.floor(this.rowWidth / this.numCols);
} else { } else {
// Desktop // Desktop
if (this.config_squareThumbs) { if (this.config.square_thumbs) {
this.numCols = Math.max(3, Math.floor(this.rowWidth / DESKTOP_ROW_HEIGHT)); this.numCols = Math.max(3, Math.floor(this.rowWidth / DESKTOP_ROW_HEIGHT));
this.rowHeight = Math.floor(this.rowWidth / this.numCols); this.rowHeight = Math.floor(this.rowWidth / this.numCols);
} else { } else {
@ -557,7 +558,7 @@ export default defineComponent({
// Folder // Folder
if (this.$route.name === 'folders') { if (this.$route.name === 'folders') {
const path = utils.getFolderRoutePath(this.config_foldersPath); const path = utils.getFolderRoutePath(this.config.folders_path);
API.DAYS_FILTER(query, DaysFilterType.FOLDER, path); API.DAYS_FILTER(query, DaysFilterType.FOLDER, path);
if (this.$route.query.recursive) { if (this.$route.query.recursive) {
API.DAYS_FILTER(query, DaysFilterType.RECURSIVE); API.DAYS_FILTER(query, DaysFilterType.RECURSIVE);
@ -589,7 +590,7 @@ export default defineComponent({
API.DAYS_FILTER(query, filter, `${user}/${name}`); API.DAYS_FILTER(query, filter, `${user}/${name}`);
// Face rect // Face rect
if (this.config_showFaceRect) { if (this.config.show_face_rect) {
API.DAYS_FILTER(query, DaysFilterType.FACE_RECT); API.DAYS_FILTER(query, DaysFilterType.FACE_RECT);
} }
} }
@ -639,7 +640,7 @@ export default defineComponent({
} }
// Get subfolders URL // Get subfolders URL
const folder = utils.getFolderRoutePath(this.config_foldersPath); const folder = utils.getFolderRoutePath(this.config.folders_path);
const url = API.Q(API.FOLDERS_SUB(), { folder }); const url = API.Q(API.FOLDERS_SUB(), { folder });
// Make API call to get subfolders // Make API call to get subfolders
@ -654,7 +655,7 @@ export default defineComponent({
} }
// Filter out hidden folders // Filter out hidden folders
if (!this.config_showHidden) { if (!this.config.show_hidden_folders) {
this.folders = this.folders.filter((f) => !f.name.startsWith('.') && f.previews?.length); this.folders = this.folders.filter((f) => !f.name.startsWith('.') && f.previews?.length);
} }
}, },
@ -730,6 +731,8 @@ export default defineComponent({
/** Process the data for days call including folders */ /** Process the data for days call including folders */
async processDays(data: IDay[]) { async processDays(data: IDay[]) {
if (!data || !this.state) return;
const list: typeof this.list = []; const list: typeof this.list = [];
const heads: typeof this.heads = {}; const heads: typeof this.heads = {};
@ -941,7 +944,7 @@ export default defineComponent({
* @param data photos * @param data photos
*/ */
processDay(dayId: number, data: IPhoto[]) { processDay(dayId: number, data: IPhoto[]) {
if (!data) return; if (!data || !this.state) return;
const head = this.heads[dayId]; const head = this.heads[dayId];
if (!head) return; if (!head) return;
@ -964,7 +967,7 @@ export default defineComponent({
} }
// Force all to square // Force all to square
const squareMode = this.isMobileLayout() || this.config_squareThumbs; const squareMode = this.isMobileLayout() || this.config.square_thumbs;
// Create justified layout with correct params // Create justified layout with correct params
const justify = getLayout( const justify = getLayout(

View File

@ -70,7 +70,7 @@ export default defineComponent({
.slice(2) as string[]; .slice(2) as string[];
// Remove base path if present // Remove base path if present
const basePath = this.config_foldersPath.split('/').filter((x) => x); const basePath = this.config.folders_path.split('/').filter((x) => x);
if (path.length >= basePath.length && path.slice(0, basePath.length).every((x, i) => x === basePath[i])) { if (path.length >= basePath.length && path.slice(0, basePath.length).every((x, i) => x === basePath[i])) {
path.splice(0, basePath.length); path.splice(0, basePath.length);
} }

View File

@ -71,7 +71,7 @@ export default defineComponent({
}, },
async chooseFolderPath() { async chooseFolderPath() {
let destination = await this.chooseFolderModal(this.t('memories', 'Choose a folder'), this.config_foldersPath); let destination = await this.chooseFolderModal(this.t('memories', 'Choose a folder'), this.config.folders_path);
// Fails if the target exists, same behavior with Nextcloud files implementation. // Fails if the target exists, same behavior with Nextcloud files implementation.
const gen = dav.movePhotos(this.photos, destination, false); const gen = dav.movePhotos(this.photos, destination, false);
this.processing = true; this.processing = true;

View File

@ -71,12 +71,12 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { showError } from '@nextcloud/dialogs'; import { showError } from '@nextcloud/dialogs';
import { loadState } from '@nextcloud/initial-state';
import axios from '@nextcloud/axios'; import axios from '@nextcloud/axios';
import NcListItem from '@nextcloud/vue/dist/Components/NcListItem'; import NcListItem from '@nextcloud/vue/dist/Components/NcListItem';
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon'; import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon';
import Modal from './Modal.vue'; import Modal from './Modal.vue';
import UserConfig from '../../mixins/UserConfig';
import { IPhoto } from '../../types'; import { IPhoto } from '../../types';
import { API } from '../../services/API'; import { API } from '../../services/API';
@ -88,9 +88,6 @@ import LargePhotoIcon from 'vue-material-design-icons/ImageArea.vue';
import LinkIcon from 'vue-material-design-icons/LinkVariant.vue'; import LinkIcon from 'vue-material-design-icons/LinkVariant.vue';
import FileIcon from 'vue-material-design-icons/File.vue'; import FileIcon from 'vue-material-design-icons/File.vue';
// Is video transcoding enabled?
const config_vodDisable = loadState('memories', 'vod_disable', true);
export default defineComponent({ export default defineComponent({
name: 'ShareModal', name: 'ShareModal',
@ -105,6 +102,8 @@ export default defineComponent({
FileIcon, FileIcon,
}, },
mixins: [UserConfig],
data: () => { data: () => {
return { return {
photo: null as IPhoto | null, photo: null as IPhoto | null,
@ -128,7 +127,7 @@ export default defineComponent({
}, },
canShareHighRes() { canShareHighRes() {
return !this.isVideo || !config_vodDisable; return !this.isVideo || !this.config.vod_disable;
}, },
canShareLink() { canShareLink() {

View File

@ -18,7 +18,7 @@
<NcActionRadio <NcActionRadio
name="sort" name="sort"
:aria-label="t('memories', 'Sort by date')" :aria-label="t('memories', 'Sort by date')"
:checked="config_albumListSort === 1" :checked="config.album_list_sort === 1"
@change="changeSort(1)" @change="changeSort(1)"
close-after-click close-after-click
> >
@ -29,7 +29,7 @@
<NcActionRadio <NcActionRadio
name="sort" name="sort"
:aria-label="t('memories', 'Sort by name')" :aria-label="t('memories', 'Sort by name')"
:checked="config_albumListSort === 2" :checked="config.album_list_sort === 2"
@change="changeSort(2)" @change="changeSort(2)"
close-after-click close-after-click
> >
@ -194,8 +194,8 @@ export default defineComponent({
* 1 = date, 2 = name * 1 = date, 2 = name
*/ */
changeSort(order: 1 | 2) { changeSort(order: 1 | 2) {
this.config_albumListSort = order; this.config.album_list_sort = order;
this.updateSetting('albumListSort'); this.updateSetting('album_list_sort');
}, },
}, },
}); });

View File

@ -25,7 +25,7 @@
</NcActionButton> </NcActionButton>
<NcActionCheckbox <NcActionCheckbox
:aria-label="t('memories', 'Mark person in preview')" :aria-label="t('memories', 'Mark person in preview')"
:checked.sync="config_showFaceRect" :checked.sync="config.show_face_rect"
@change="changeShowFaceRect" @change="changeShowFaceRect"
> >
{{ t('memories', 'Mark person in preview') }} {{ t('memories', 'Mark person in preview') }}
@ -104,10 +104,8 @@ export default defineComponent({
}, },
changeShowFaceRect() { changeShowFaceRect() {
localStorage.setItem('memories_showFaceRect', this.config_showFaceRect ? '1' : '0'); this.updateSetting('show_face_rect');
setTimeout(() => { setTimeout(() => this.$router.go(0), 100); // refresh page
this.$router.go(0); // refresh page
}, 500);
}, },
}, },
}); });

View File

@ -108,7 +108,7 @@ export default defineComponent({
}, },
share() { share() {
globalThis.shareNodeLink(utils.getFolderRoutePath(this.config_foldersPath)); globalThis.shareNodeLink(utils.getFolderRoutePath(this.config.folders_path));
}, },
}, },
}); });

View File

@ -1,5 +1,5 @@
import PhotoSwipe from 'photoswipe'; import PhotoSwipe from 'photoswipe';
import { loadState } from '@nextcloud/initial-state'; import staticConfig from '../../services/static-config';
import { showError } from '@nextcloud/dialogs'; import { showError } from '@nextcloud/dialogs';
import { translate as t } from '@nextcloud/l10n'; import { translate as t } from '@nextcloud/l10n';
import { getCurrentUser } from '@nextcloud/auth'; import { getCurrentUser } from '@nextcloud/auth';
@ -25,10 +25,6 @@ type PsVideoEvent = PsEvent & {
content: VideoContent; content: VideoContent;
}; };
const config_vodDisable = loadState('memories', 'vod_disable', true);
const config_video_default_quality = Number(loadState('memories', 'video_default_quality', <string>'0') as string);
/** /**
* Check if slide has video content * Check if slide has video content
*/ */
@ -155,7 +151,7 @@ class VideoContentSetup {
type: string; type: string;
}[] = []; }[] = [];
if (!config_vodDisable) { if (!staticConfig.getSync('vod_disable')) {
sources.push(this.getHLSsrc(content)); sources.push(this.getHLSsrc(content));
} }
@ -205,7 +201,7 @@ class VideoContentSetup {
directFailed = true; directFailed = true;
console.warn('PsVideo: Direct video stream could not be opened.'); console.warn('PsVideo: Direct video stream could not be opened.');
if (!hlsFailed && !config_vodDisable) { if (!hlsFailed && !staticConfig.getSync('vod_disable')) {
console.warn('PsVideo: Trying HLS stream'); console.warn('PsVideo: Trying HLS stream');
vjs.src(this.getHLSsrc(content)); vjs.src(this.getHLSsrc(content));
} }
@ -336,7 +332,7 @@ class VideoContentSetup {
// Add quality options // Add quality options
if (qualityNums) { if (qualityNums) {
opts.quality = { opts.quality = {
default: config_video_default_quality, default: Number(staticConfig.getSync('video_default_quality')),
options: qualityNums, options: qualityNums,
forced: true, forced: true,
onChange: (quality: number) => { onChange: (quality: number) => {

View File

@ -770,7 +770,7 @@ export default defineComponent({
// Get full image URL // Get full image URL
const fullUrl = isvideo ? null : API.IMAGE_DECODABLE(photo.fileid, photo.etag); const fullUrl = isvideo ? null : API.IMAGE_DECODABLE(photo.fileid, photo.etag);
const fullLoadCond = this.config_fullResAlways ? 'always' : this.config_fullResOnZoom ? 'zoom' : 'never'; const fullLoadCond = this.config.full_res_always ? 'always' : this.config.full_res_on_zoom ? 'zoom' : 'never';
return { return {
src: previewUrl, src: previewUrl,

View File

@ -1,43 +1,30 @@
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus';
import { loadState } from '@nextcloud/initial-state';
import axios from '@nextcloud/axios'; import axios from '@nextcloud/axios';
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus';
import { API } from '../services/API'; import { API } from '../services/API';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import { IConfig } from '../types';
import staticConfig from '../services/static-config';
const eventName = 'memories:user-config-changed'; const eventName = 'memories:user-config-changed';
const localSettings = ['squareThumbs', 'fullResOnZoom', 'fullResAlways', 'showFaceRect', 'albumListSort']; const localSettings: (keyof IConfig)[] = [
'square_thumbs',
'full_res_on_zoom',
'full_res_always',
'show_face_rect',
'album_list_sort',
];
export default defineComponent({ export default defineComponent({
name: 'UserConfig', name: 'UserConfig',
data: () => ({ data: () => ({
config_timelinePath: loadState('memories', 'timelinePath', <string>'') as string, config: { ...staticConfig.getDefault() },
config_foldersPath: loadState('memories', 'foldersPath', <string>'/') as string, configEventName: eventName,
config_showHidden: loadState('memories', 'showHidden', <string>'false') === 'true',
config_sortFolderMonth: loadState('memories', 'sortFolderMonth', <string>'false') === 'true',
config_sortAlbumMonth: loadState('memories', 'sortAlbumMonth', <string>'true') === 'true',
config_enableTopMemories: loadState('memories', 'enableTopMemories', <string>'false') === 'true',
config_tagsEnabled: Boolean(loadState('memories', 'systemtags', <string>'')),
config_recognizeEnabled: Boolean(loadState('memories', 'recognize', <string>'')),
config_facerecognitionInstalled: Boolean(loadState('memories', 'facerecognitionInstalled', <string>'')),
config_facerecognitionEnabled: Boolean(loadState('memories', 'facerecognitionEnabled', <string>'')),
config_albumsEnabled: Boolean(loadState('memories', 'albums', <string>'')),
config_placesGis: Number(loadState('memories', 'places_gis', <string>'-1')),
config_squareThumbs: localStorage.getItem('memories_squareThumbs') === '1',
config_fullResOnZoom: localStorage.getItem('memories_fullResOnZoom') !== '0',
config_fullResAlways: localStorage.getItem('memories_fullResAlways') === '1',
config_showFaceRect: localStorage.getItem('memories_showFaceRect') === '1',
config_albumListSort: Number(localStorage.getItem('memories_albumListSort') || 1),
config_eventName: eventName,
}), }),
created() { created() {
subscribe(eventName, this.updateLocalSetting); subscribe(eventName, this.updateLocalSetting);
this.refreshFromConfig();
}, },
beforeDestroy() { beforeDestroy() {
@ -45,27 +32,32 @@ export default defineComponent({
}, },
methods: { methods: {
updateLocalSetting({ setting, value }) { async refreshFromConfig() {
this['config_' + setting] = value; const config = await staticConfig.getAll();
const changed = Object.keys(config).filter((key) => config[key] !== this.config[key]);
if (changed.length === 0) return;
changed.forEach((key) => (this.config[key] = config[key]));
emit(eventName, { setting: null, value: null });
}, },
async updateSetting(setting: string) { updateLocalSetting({ setting, value }) {
const value = this['config_' + setting]; if (setting) {
this.config[setting] = value;
}
},
if (localSettings.includes(setting)) { async updateSetting<K extends keyof IConfig>(setting: K, remote?: string) {
if (typeof value === 'boolean') { const value = this.config[setting];
localStorage.setItem('memories_' + setting, value ? '1' : '0');
} else { if (!localSettings.includes(setting)) {
localStorage.setItem('memories_' + setting, value); await axios.put(API.CONFIG(remote ?? setting), {
}
} else {
// Long time save setting
await axios.put(API.CONFIG(setting), {
value: value.toString(), value: value.toString(),
}); });
} }
// Visible elements update setting staticConfig.setLs(setting, value);
emit(eventName, { setting, value }); emit(eventName, { setting, value });
}, },
}, },

View File

@ -185,6 +185,10 @@ export class API {
return gen(`${BASE}/config/{setting}`, { setting }); return gen(`${BASE}/config/{setting}`, { setting });
} }
static CONFIG_GET() {
return gen(`${BASE}/config`);
}
static SYSTEM_CONFIG(setting: string | null) { static SYSTEM_CONFIG(setting: string | null) {
return setting ? gen(`${BASE}/system-config/{setting}`, { setting }) : gen(`${BASE}/system-config`); return setting ? gen(`${BASE}/system-config/{setting}`, { setting }) : gen(`${BASE}/system-config`);
} }

View File

@ -0,0 +1,111 @@
import axios from '@nextcloud/axios';
import { API } from './API';
import { IConfig } from '../types';
class StaticConfig {
private config: IConfig | null = null;
private initPromises: Array<() => void> = [];
private default: IConfig | null = null;
public constructor() {
this.init();
}
private async init() {
const res = await axios.get<IConfig>(API.CONFIG_GET());
this.config = res.data;
this.getDefault();
for (const key in this.config) {
this.default![key] = this.config[key];
this.setLs(key as keyof IConfig, this.config[key]);
}
this.initPromises.forEach((resolve) => resolve());
}
private async waitForInit() {
if (!this.config) {
await new Promise<void>((resolve) => {
this.initPromises.push(resolve);
});
}
}
public async getAll() {
await this.waitForInit();
return this.config!;
}
public async get<K extends keyof IConfig>(key: K) {
await this.waitForInit();
return this.config![key];
}
public getSync<K extends keyof IConfig>(key: K) {
return this.getDefault()[key];
}
public setLs<K extends keyof IConfig>(key: K, value: IConfig[K]) {
if (this.default) {
this.default[key] = value;
}
if (this.config) {
this.config[key] = value;
}
localStorage.setItem(`memories_${key}`, value.toString());
}
public getDefault(): IConfig {
if (this.default) {
return this.default;
}
const config: IConfig = {
version: '',
vod_disable: false,
video_default_quality: '0',
places_gis: -1,
systemtags_enabled: false,
recognize_enabled: false,
albums_enabled: false,
facerecognition_installed: false,
facerecognition_enabled: false,
timeline_path: '',
folders_path: '',
show_hidden_folders: false,
sort_folder_month: false,
sort_album_month: true,
enable_top_memories: true,
square_thumbs: false,
full_res_on_zoom: true,
full_res_always: false,
show_face_rect: false,
album_list_sort: 1,
};
for (const key in config) {
const val = localStorage.getItem(`memories_${key}`);
if (val !== null) {
if (typeof config[key] === 'boolean') {
config[key] = val === 'true';
} else if (typeof config[key] === 'number') {
config[key] = Number(val);
} else {
config[key] = val;
}
}
}
this.default = config;
return config;
}
}
export default new StaticConfig();

View File

@ -1,8 +1,6 @@
import { loadState } from '@nextcloud/initial-state'; import staticConfig from './static-config';
import { translate as t } from '@nextcloud/l10n'; import { translate as t } from '@nextcloud/l10n';
const config_facerecognitionEnabled = Boolean(loadState('memories', 'facerecognitionEnabled', <string>''));
type RouteNameType = string | null | undefined; type RouteNameType = string | null | undefined;
export function emptyDescription(routeName: RouteNameType): string { export function emptyDescription(routeName: RouteNameType): string {
@ -14,7 +12,7 @@ export function emptyDescription(routeName: RouteNameType): string {
case 'thisday': case 'thisday':
return t('memories', 'Memories from past years will appear here'); return t('memories', 'Memories from past years will appear here');
case 'facerecognition': case 'facerecognition':
return config_facerecognitionEnabled return staticConfig.getSync('facerecognition_enabled')
? t('memories', 'You will find your friends soon. Please be patient') ? t('memories', 'You will find your friends soon. Please be patient')
: t('memories', 'Face Recognition is disabled. Enable in settings to find your friends'); : t('memories', 'Face Recognition is disabled. Enable in settings to find your friends');
case 'videos': case 'videos':

View File

@ -1,18 +1,23 @@
import { getCurrentUser } from '@nextcloud/auth'; import { getCurrentUser } from '@nextcloud/auth';
import { loadState } from '@nextcloud/initial-state'; import staticConfig from '../static-config';
/** Cache keys */ /** Cache keys */
const memoriesVersion: string = loadState('memories', 'version', '');
const uid = getCurrentUser()?.uid || 'guest'; const uid = getCurrentUser()?.uid || 'guest';
const cacheName = `memories-${memoriesVersion}-${uid}`;
async function getCacheName() {
const memoriesVersion = await staticConfig.get('version');
return `memories-${memoriesVersion}-${uid}`;
}
// Clear all caches except the current one // Clear all caches except the current one
(async function clearCaches() { (async function clearCaches() {
if (!memoriesVersion || uid === 'guest') return; if (uid === 'guest') return;
const keys = await window.caches?.keys(); const keys = await window.caches?.keys();
if (!keys?.length) return; if (!keys?.length) return;
const cacheName = await getCacheName();
for (const key of keys) { for (const key of keys) {
if (key.startsWith('memories-') && key !== cacheName) { if (key.startsWith('memories-') && key !== cacheName) {
window.caches.delete(key); window.caches.delete(key);
@ -23,10 +28,8 @@ const cacheName = `memories-${memoriesVersion}-${uid}`;
/** Singleton cache instance */ /** Singleton cache instance */
let staticCache: Cache | null = null; let staticCache: Cache | null = null;
export async function openCache() { export async function openCache() {
if (!memoriesVersion) return null;
try { try {
return (staticCache ??= (await window.caches?.open(cacheName)) ?? null); return (staticCache ??= (await window.caches?.open(await getCacheName())) ?? null);
} catch { } catch {
return null; return null;
} }

View File

@ -236,3 +236,29 @@ export type ISelectionAction = {
/** Allow for public routes (default false) */ /** Allow for public routes (default false) */
allowPublic?: boolean; allowPublic?: boolean;
}; };
export type IConfig = {
version: string;
vod_disable: boolean;
video_default_quality: string;
places_gis: number;
systemtags_enabled: boolean;
recognize_enabled: boolean;
albums_enabled: boolean;
facerecognition_installed: boolean;
facerecognition_enabled: boolean;
timeline_path: string;
folders_path: string;
show_hidden_folders: boolean;
sort_folder_month: boolean;
sort_album_month: boolean;
enable_top_memories: boolean;
square_thumbs: boolean;
full_res_on_zoom: boolean;
full_res_always: boolean;
show_face_rect: boolean;
album_list_sort: 1 | 2;
};

23
src/vue-globals.d.ts vendored
View File

@ -10,29 +10,6 @@ declare module 'vue' {
c: typeof constants.c; c: typeof constants.c;
state_noDownload: boolean; state_noDownload: boolean;
// UserConfig.ts
config_timelinePath: string;
config_foldersPath: string;
config_showHidden: boolean;
config_sortFolderMonth: boolean;
config_sortAlbumMonth: boolean;
config_tagsEnabled: boolean;
config_recognizeEnabled: boolean;
config_facerecognitionInstalled: boolean;
config_facerecognitionEnabled: boolean;
config_albumsEnabled: boolean;
config_placesGis: number;
config_squareThumbs: boolean;
config_enableTopMemories: boolean;
config_fullResOnZoom: boolean;
config_fullResAlways: boolean;
config_showFaceRect: boolean;
config_albumListSort: 1 | 2;
config_eventName: string;
updateSetting: (setting: string) => Promise<void>;
updateLocalSetting: (opts: { setting: string; value: any }) => void;
} }
} }