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
['name' => 'Other#setUserConfig', 'url' => '/api/config/{key}', 'verb' => 'PUT'],
['name' => 'Other#getUserConfig', 'url' => '/api/config', 'verb' => 'GET'],
// Admin
['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
*

View File

@ -58,35 +58,6 @@ class PageController extends Controller
OCPUtil::addScript($this->appName, 'memories-main');
$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
if (Util::recognizeIsEnabled()) {
// Auto translation for tags
@ -138,22 +109,6 @@ class PageController extends Controller
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
*

View File

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

View File

@ -111,7 +111,6 @@ class PublicController extends AuthPublicShareController
// Scripts
\OCP\Util::addScript($this->appName, 'memories-main');
PageController::provideCommonInitialState($this->initialState);
// Share info
$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 { 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 UserConfig from './mixins/UserConfig';
@ -137,11 +137,11 @@ export default defineComponent({
},
recognize(): string | false {
if (!this.config_recognizeEnabled) {
if (!this.config.recognize_enabled) {
return false;
}
if (this.config_facerecognitionInstalled) {
if (this.config.facerecognition_enabled) {
return t('memories', 'People (Recognize)');
}
@ -149,11 +149,11 @@ export default defineComponent({
},
facerecognition(): string | false {
if (!this.config_facerecognitionInstalled) {
if (!this.config.facerecognition_installed) {
return false;
}
if (this.config_recognizeEnabled) {
if (this.config.recognize_enabled) {
return t('memories', 'People (Face Recognition)');
}
@ -161,11 +161,15 @@ export default defineComponent({
},
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 {
return this.config_albumsEnabled;
return this.config.albums_enabled;
},
removeOuterGap(): boolean {
@ -197,13 +201,14 @@ export default defineComponent({
window.addEventListener('resize', () => {
utils.setRenewingTimeout(this, 'resizeTimer', onResize, 100);
});
// Register navigation items on config change
subscribe(this.configEventName, this.refreshNav);
},
mounted() {
this.doRouteChecks();
// Populate navigation
this.navItems = this.navItemsAll().filter((item) => typeof item.if === 'undefined' || Boolean(item.if));
this.refreshNav();
// Store CSS variables modified
const root = document.documentElement;
@ -255,8 +260,8 @@ export default defineComponent({
},
methods: {
navItemsAll(): NavItem[] {
return [
refreshNav() {
const navItems = [
{
name: 'timeline',
icon: ImageMultiple,
@ -309,7 +314,7 @@ export default defineComponent({
name: 'places',
icon: MarkerIcon,
title: t('memories', 'Places'),
if: this.config_placesGis > 0,
if: this.config.places_gis > 0,
},
{
name: 'map',
@ -320,9 +325,11 @@ export default defineComponent({
name: 'tags',
icon: TagsIcon,
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() {

View File

@ -53,11 +53,11 @@ export default defineComponent({
},
created() {
subscribe(this.config_eventName, this.routeChange);
subscribe(this.configEventName, this.routeChange);
},
beforeDestroy() {
unsubscribe(this.config_eventName, this.routeChange);
unsubscribe(this.configEventName, this.routeChange);
},
watch: {
@ -74,7 +74,7 @@ export default defineComponent({
this.loading++;
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') {
this.items = await dav.getTags();
} else if (route === 'recognize' || route === 'facerecognition') {

View File

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

View File

@ -254,7 +254,7 @@ export default defineComponent({
/** Is album route */
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 */
@ -786,7 +786,7 @@ export default defineComponent({
* Move selected photos to another person
*/
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'));
return;
}

View File

@ -30,39 +30,51 @@
>
<NcAppSettingsSection id="general-settings" :title="t('memories', 'General')">
<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') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch
:checked.sync="config_enableTopMemories"
:checked.sync="config.enable_top_memories"
@update:checked="updateEnableTopMemories"
type="switch"
>
{{ t('memories', 'Show past photos on top of timeline') }}
</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') }}
</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)') }}
</NcCheckboxRadioSwitch>
</NcAppSettingsSection>
<NcAppSettingsSection id="folders-settings" :title="t('memories', 'Folders')">
<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') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch
:checked.sync="config_sortFolderMonth"
:checked.sync="config.sort_folder_month"
@update:checked="updateSortFolderMonth"
type="switch"
>
@ -72,7 +84,7 @@
<NcAppSettingsSection id="albums-settings" :title="t('memories', 'Albums')">
<NcCheckboxRadioSwitch
:checked.sync="config_sortAlbumMonth"
:checked.sync="config.sort_album_month"
@update:checked="updateSortAlbumMonth"
type="switch"
>
@ -147,57 +159,57 @@ export default defineComponent({
},
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[]) {
if (!paths || !paths.length) return;
const newPath = paths.join(';');
if (newPath !== this.config_timelinePath) {
this.config_timelinePath = newPath;
await this.updateSetting('timelinePath');
if (newPath !== this.config.timeline_path) {
this.config.timeline_path = newPath;
await this.updateSetting('timeline_path', 'timelinePath');
}
},
async chooseFoldersPath() {
let newPath = await this.chooseFolder(
this.t('memories', 'Choose the root for the folders view'),
this.config_foldersPath
this.config.folders_path
);
if (newPath === '') newPath = '/';
if (newPath !== this.config_foldersPath) {
this.config_foldersPath = newPath;
await this.updateSetting('foldersPath');
if (newPath !== this.config.folders_path) {
this.config.folders_path = newPath;
await this.updateSetting('folders_path', 'foldersPath');
}
},
async updateSquareThumbs() {
await this.updateSetting('squareThumbs');
await this.updateSetting('square_thumbs');
},
async updateFullResOnZoom() {
await this.updateSetting('fullResOnZoom');
await this.updateSetting('full_res_on_zoom');
},
async updateFullResAlways() {
await this.updateSetting('fullResAlways');
await this.updateSetting('full_res_always');
},
async updateEnableTopMemories() {
await this.updateSetting('enableTopMemories');
await this.updateSetting('enable_top_memories', 'enableTopMemories');
},
async updateShowHidden() {
await this.updateSetting('showHidden');
await this.updateSetting('show_hidden_folders', 'showHidden');
},
async updateSortFolderMonth() {
await this.updateSetting('sortFolderMonth');
await this.updateSetting('sort_folder_month', 'sortFolderMonth');
},
async updateSortAlbumMonth() {
await this.updateSetting('sortAlbumMonth');
await this.updateSetting('sort_album_month', 'sortAlbumMonth');
},
},
});

View File

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

View File

@ -70,7 +70,7 @@ export default defineComponent({
.slice(2) as string[];
// 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])) {
path.splice(0, basePath.length);
}

View File

@ -71,7 +71,7 @@ export default defineComponent({
},
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.
const gen = dav.movePhotos(this.photos, destination, false);
this.processing = true;

View File

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

View File

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

View File

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

View File

@ -108,7 +108,7 @@ export default defineComponent({
},
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 { loadState } from '@nextcloud/initial-state';
import staticConfig from '../../services/static-config';
import { showError } from '@nextcloud/dialogs';
import { translate as t } from '@nextcloud/l10n';
import { getCurrentUser } from '@nextcloud/auth';
@ -25,10 +25,6 @@ type PsVideoEvent = PsEvent & {
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
*/
@ -155,7 +151,7 @@ class VideoContentSetup {
type: string;
}[] = [];
if (!config_vodDisable) {
if (!staticConfig.getSync('vod_disable')) {
sources.push(this.getHLSsrc(content));
}
@ -205,7 +201,7 @@ class VideoContentSetup {
directFailed = true;
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');
vjs.src(this.getHLSsrc(content));
}
@ -336,7 +332,7 @@ class VideoContentSetup {
// Add quality options
if (qualityNums) {
opts.quality = {
default: config_video_default_quality,
default: Number(staticConfig.getSync('video_default_quality')),
options: qualityNums,
forced: true,
onChange: (quality: number) => {

View File

@ -770,7 +770,7 @@ export default defineComponent({
// Get full image URL
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 {
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 { emit, subscribe, unsubscribe } from '@nextcloud/event-bus';
import { API } from '../services/API';
import { defineComponent } from 'vue';
import { IConfig } from '../types';
import staticConfig from '../services/static-config';
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({
name: 'UserConfig',
data: () => ({
config_timelinePath: loadState('memories', 'timelinePath', <string>'') as string,
config_foldersPath: loadState('memories', 'foldersPath', <string>'/') as string,
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,
config: { ...staticConfig.getDefault() },
configEventName: eventName,
}),
created() {
subscribe(eventName, this.updateLocalSetting);
this.refreshFromConfig();
},
beforeDestroy() {
@ -45,27 +32,32 @@ export default defineComponent({
},
methods: {
updateLocalSetting({ setting, value }) {
this['config_' + setting] = value;
async refreshFromConfig() {
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) {
const value = this['config_' + setting];
if (localSettings.includes(setting)) {
if (typeof value === 'boolean') {
localStorage.setItem('memories_' + setting, value ? '1' : '0');
} else {
localStorage.setItem('memories_' + setting, value);
updateLocalSetting({ setting, value }) {
if (setting) {
this.config[setting] = value;
}
} else {
// Long time save setting
await axios.put(API.CONFIG(setting), {
},
async updateSetting<K extends keyof IConfig>(setting: K, remote?: string) {
const value = this.config[setting];
if (!localSettings.includes(setting)) {
await axios.put(API.CONFIG(remote ?? setting), {
value: value.toString(),
});
}
// Visible elements update setting
staticConfig.setLs(setting, value);
emit(eventName, { setting, value });
},
},

View File

@ -185,6 +185,10 @@ export class API {
return gen(`${BASE}/config/{setting}`, { setting });
}
static CONFIG_GET() {
return gen(`${BASE}/config`);
}
static SYSTEM_CONFIG(setting: string | null) {
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';
const config_facerecognitionEnabled = Boolean(loadState('memories', 'facerecognitionEnabled', <string>''));
type RouteNameType = string | null | undefined;
export function emptyDescription(routeName: RouteNameType): string {
@ -14,7 +12,7 @@ export function emptyDescription(routeName: RouteNameType): string {
case 'thisday':
return t('memories', 'Memories from past years will appear here');
case 'facerecognition':
return config_facerecognitionEnabled
return staticConfig.getSync('facerecognition_enabled')
? t('memories', 'You will find your friends soon. Please be patient')
: t('memories', 'Face Recognition is disabled. Enable in settings to find your friends');
case 'videos':

View File

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

View File

@ -236,3 +236,29 @@ export type ISelectionAction = {
/** Allow for public routes (default false) */
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;
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;
}
}