fragment: fix logic

Signed-off-by: Varun Patil <radialapps@gmail.com>
pull/888/head
Varun Patil 2023-10-24 00:43:25 -07:00
parent 3cd2402c4e
commit a0f40dfcae
6 changed files with 69 additions and 53 deletions

View File

@ -67,6 +67,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import type { Route } from 'vue-router';
import NcContent from '@nextcloud/vue/dist/Components/NcContent'; import NcContent from '@nextcloud/vue/dist/Components/NcContent';
import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent'; import NcAppContent from '@nextcloud/vue/dist/Components/NcAppContent';
@ -76,6 +77,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 fragment from './services/fragment';
import * as utils from './services/utils'; import * as utils from './services/utils';
import * as nativex from './native'; import * as nativex from './native';
import staticConfig from './services/static-config'; import staticConfig from './services/static-config';
@ -159,6 +161,13 @@ export default defineComponent({
settingsOpen: false, settingsOpen: false,
}), }),
watch: {
async $route(to: Route, from: Route) {
// Global triggers
fragment.changeTrigger(to, from);
},
},
computed: { computed: {
native(): boolean { native(): boolean {
return nativex.has(); return nativex.has();

View File

@ -108,6 +108,7 @@ import EmptyContent from './top-matter/EmptyContent.vue';
import TopMatter from './top-matter/TopMatter.vue'; import TopMatter from './top-matter/TopMatter.vue';
import DynamicTopMatter from './top-matter/DynamicTopMatter.vue'; import DynamicTopMatter from './top-matter/DynamicTopMatter.vue';
import fragment from '../services/fragment';
import * as dav from '../services/dav'; import * as dav from '../services/dav';
import * as utils from '../services/utils'; import * as utils from '../services/utils';
import * as nativex from '../native'; import * as nativex from '../native';
@ -265,13 +266,11 @@ export default defineComponent({
await this.softRefreshInternal(true); await this.softRefreshInternal(true);
} }
// The viewer might change the route immediately again // Check if viewer is supposed to be open
await this.$nextTick(); if (from?.hash !== to.hash && !_m.viewer.isOpen && fragment.viewer) {
// Check if hash has changed
if (from?.hash !== to.hash && !_m.viewer.isOpen && utils.fragment.viewer.open) {
// Open viewer // Open viewer
const { dayid, key } = utils.fragment.viewer; const [dayidStr, key] = fragment.viewer.args;
const dayid = parseInt(dayidStr);
if (isNaN(dayid) || !key) return; if (isNaN(dayid) || !key) return;
// Get day // Get day
@ -295,9 +294,6 @@ export default defineComponent({
} }
_m.viewer.openDynamic(photo, this.list); _m.viewer.openDynamic(photo, this.list);
} else if (!utils.fragment.viewer.open && _m.viewer.isOpen) {
// No viewer fragment but viewer is open
_m.viewer.close();
} }
}, },

View File

@ -183,6 +183,7 @@ import { showError } from '@nextcloud/dialogs';
import axios from '@nextcloud/axios'; import axios from '@nextcloud/axios';
import { API } from '../../services/API'; import { API } from '../../services/API';
import fragment from '../../services/fragment';
import * as dav from '../../services/dav'; import * as dav from '../../services/dav';
import * as utils from '../../services/utils'; import * as utils from '../../services/utils';
import * as nativex from '../../native'; import * as nativex from '../../native';
@ -283,6 +284,7 @@ export default defineComponent({
utils.bus.on('files:file:created', this.handleFileUpdated); utils.bus.on('files:file:created', this.handleFileUpdated);
utils.bus.on('files:file:updated', this.handleFileUpdated); utils.bus.on('files:file:updated', this.handleFileUpdated);
utils.bus.on('memories:window:resize', this.handleWindowResize); utils.bus.on('memories:window:resize', this.handleWindowResize);
utils.bus.on('memories:fragment:pop:viewer', this.close);
// The viewer is a singleton // The viewer is a singleton
const self = this; const self = this;
@ -306,6 +308,7 @@ export default defineComponent({
utils.bus.off('files:file:created', this.handleFileUpdated); utils.bus.off('files:file:created', this.handleFileUpdated);
utils.bus.off('files:file:updated', this.handleFileUpdated); utils.bus.off('files:file:updated', this.handleFileUpdated);
utils.bus.off('memories:window:resize', this.handleWindowResize); utils.bus.off('memories:window:resize', this.handleWindowResize);
utils.bus.off('memories:fragment:pop:viewer', this.close);
}, },
computed: { computed: {
@ -768,6 +771,7 @@ export default defineComponent({
/** Close the viewer */ /** Close the viewer */
close() { close() {
if (!this.isOpen) return;
this.photoswipe?.close(); this.photoswipe?.close();
}, },
@ -867,15 +871,17 @@ export default defineComponent({
/** Set the route hash to the given photo */ /** Set the route hash to the given photo */
setFragment(photo: IPhoto | null) { setFragment(photo: IPhoto | null) {
// Add or update fragment
if (photo) { if (photo) {
const frag = utils.fragment.viewer; return fragment.push({
frag.dayid = photo.dayid; type: fragment.types.viewer,
frag.key = photo.key!; args: [String(photo.dayid), photo.key!],
return utils.fragment.push(frag); });
} }
// Remove fragment if closed
if (!this.isOpen) { if (!this.isOpen) {
return utils.fragment.pop('v'); return fragment.pop(fragment.types.viewer);
} }
}, },

View File

@ -1,23 +1,26 @@
import type { IPhoto } from '../../types'; import type { Route } from 'vue-router';
import * as utils from './utils';
/** Viewer Fragment */ /** Mapping of route name to key type */
type FragmentTypeViewer = 'v'; export enum FragmentType {
viewer = 'v',
}
/** All types of fragmemts */ /** Names of fragments */
type FragmentType = FragmentTypeViewer; export type FragmentName = keyof typeof FragmentType;
/** Data structure to encode to fragment */ /** Data structure to encode to fragment */
type FragmentObj = { export type Fragment = {
type: FragmentType; type: FragmentType;
args: string[]; args: string[];
index: number; index?: number;
}; };
/** /**
* Decode fragments from string. * Decode fragments from string.
* @param hash Hash string * @param hash Hash string
*/ */
function decodeFragment(hash: string): FragmentObj[] { function decodeFragment(hash: string): Fragment[] {
return hash return hash
.substring(1) // remove # at start .substring(1) // remove # at start
.split('&') // get all parts .split('&') // get all parts
@ -36,7 +39,7 @@ function decodeFragment(hash: string): FragmentObj[] {
* Encode fragments to string. * Encode fragments to string.
* @param fragments Fragments to encode * @param fragments Fragments to encode
*/ */
function encodeFragment(fragments: FragmentObj[]): string { function encodeFragment(fragments: Fragment[]): string {
if (!fragments.length) return ''; if (!fragments.length) return '';
return '#' + fragments.map((frag) => [frag.type, ...frag.args].join('/')).join('&'); return '#' + fragments.map((frag) => [frag.type, ...frag.args].join('/')).join('&');
} }
@ -46,21 +49,23 @@ function encodeFragment(fragments: FragmentObj[]): string {
*/ */
const cache = { const cache = {
hash: String(), hash: String(),
list: [] as FragmentObj[], list: [] as Fragment[],
}; };
export const fragment = { export default {
types: FragmentType,
/** /**
* Get list of all fragments in route. * Get list of all fragments in route.
* @returns List of fragments * @returns List of fragments
*/ */
get list(): FragmentObj[] { get list(): Fragment[] {
if (cache.hash !== _m.route.hash) { if (cache.hash !== _m.route.hash) {
cache.hash = _m.route.hash; cache.hash = _m.route.hash;
cache.list = decodeFragment(cache.hash ?? String()); cache.list = decodeFragment(cache.hash ?? String());
} }
return cache.list; return [...cache.list];
}, },
/** /**
@ -75,11 +80,11 @@ export const fragment = {
* Add fragment to route. * Add fragment to route.
* @param frag Fragment to add to route * @param frag Fragment to add to route
*/ */
push(frag: FragmentObj) { push(frag: Fragment) {
const list = this.list; const list = this.list;
// Get the top fragment // Get the top fragment
const top = list[list.length - 1]; const top = list.length ? list[list.length - 1] : null;
// Check if we are already on this fragment // Check if we are already on this fragment
if (top?.type === frag.type) { if (top?.type === frag.type) {
@ -125,7 +130,7 @@ export const fragment = {
if (!frag) return; if (!frag) return;
// Go back in history // Go back in history
_m.router.go(-frag.index - 1); _m.router.go(-frag.index! - 1);
// Check if the fragment still exists // Check if the fragment still exists
// In that case, replace the route to remove the fragment // In that case, replace the route to remove the fragment
@ -134,33 +139,30 @@ export const fragment = {
_m.router.replace({ _m.router.replace({
path: _m.route.path, path: _m.route.path,
query: _m.route.query, query: _m.route.query,
hash: encodeFragment(this.list.slice(0, -sfrag.index - 1)), hash: encodeFragment(this.list.slice(0, -sfrag.index! - 1)),
}); });
} }
}, },
get viewer() { get viewer() {
const frag = this.get('v'); return this.get(FragmentType.viewer);
const typed = {
open: !!frag,
type: frag?.type ?? 'v',
args: (frag?.args ?? ['0', '']) as [string, string],
index: frag?.index ?? -1,
get dayid() {
return parseInt(this.args[0]);
},
set dayid(dayid: number) {
this.args[0] = String(dayid);
}, },
get key() { changeTrigger(to: Route, from: Route) {
return this.args[1]; const toF = decodeFragment(to.hash);
}, const fromF = decodeFragment(from.hash);
set key(key: string) {
this.args[1] = key; // Emit events for popped fragments
}, fromF
}; .filter((frag) => !toF.find((f) => f.type === frag.type))
return typed; .forEach((frag) => {
for (const [key, type] of Object.entries(FragmentType)) {
const name = key as FragmentName;
if (type === frag.type) {
utils.bus.emit(`memories:fragment:pop:${name}`, frag);
break;
}
}
});
}, },
}; };

View File

@ -1,5 +1,6 @@
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'; import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus';
import { IConfig, IPhoto } from '../../types'; import type { IConfig, IPhoto } from '../../types';
import type { FragmentName, Fragment } from '../fragment';
export type BusEvent = { export type BusEvent = {
/** Open/close the navigation drawer */ /** Open/close the navigation drawer */
@ -49,6 +50,9 @@ export type BusEvent = {
/** NativeX database was updated */ /** NativeX database was updated */
'nativex:db:updated': null; 'nativex:db:updated': null;
} & {
/** A fragment was removed from the route */
[key in `memories:fragment:pop:${FragmentName}`]: Fragment;
}; };
/** /**

View File

@ -5,4 +5,3 @@ export * from './date';
export * from './helpers'; export * from './helpers';
export * from './dialog'; export * from './dialog';
export * from './event-bus'; export * from './event-bus';
export * from './fragment';