From d609f41f61d82d64cb8b01a0f4e52fb1af2c893e Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Tue, 18 Mar 2025 17:31:25 +0900 Subject: 🎨 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/global/StackingRouterView.vue | 191 +++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 packages/frontend/src/components/global/StackingRouterView.vue (limited to 'packages/frontend/src/components/global/StackingRouterView.vue') diff --git a/packages/frontend/src/components/global/StackingRouterView.vue b/packages/frontend/src/components/global/StackingRouterView.vue new file mode 100644 index 0000000000..c9364bdffa --- /dev/null +++ b/packages/frontend/src/components/global/StackingRouterView.vue @@ -0,0 +1,191 @@ + + + + + + + -- cgit v1.2.3-freya From 05391f59a5235ac7d52ee07bb3dc5ee7e2ae0e71 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Tue, 18 Mar 2025 20:55:19 +0900 Subject: enhance(frontend): improve StackingRouterView --- .../src/components/global/StackingRouterView.vue | 135 +++++++++++++-------- 1 file changed, 87 insertions(+), 48 deletions(-) (limited to 'packages/frontend/src/components/global/StackingRouterView.vue') diff --git a/packages/frontend/src/components/global/StackingRouterView.vue b/packages/frontend/src/components/global/StackingRouterView.vue index c9364bdffa..71a91d4887 100644 --- a/packages/frontend/src/components/global/StackingRouterView.vue +++ b/packages/frontend/src/components/global/StackingRouterView.vue @@ -11,11 +11,14 @@ SPDX-License-Identifier: AGPL-3.0-only :leaveToClass="prefer.s.animation ? $style.transition_x_leaveTo : ''" :moveClass="prefer.s.animation ? $style.transition_x_move : ''" :duration="200" - tag="div" :class="$style.root" + tag="div" :class="$style.tabs" > -
+
-
+
+
+ +
@@ -52,28 +55,27 @@ if (router == null) { const currentDepth = inject(DI.routerCurrentDepth, 0); provide(DI.routerCurrentDepth, currentDepth + 1); -const current = router.current!; -const key = ref(router.getCurrentKey() + JSON.stringify(Object.fromEntries(current.props))); - const tabs = shallowRef([{ - key: key.value, + key: router.getCurrentPath(), path: router.getCurrentPath(), - component: 'component' in current.route ? current.route.component : MkLoadingPage, - props: current.props, + route: router.current.route.path, + component: 'component' in router.current.route ? router.current.route.component : MkLoadingPage, + props: router.current.props, }]); -function onChange({ resolved, key: newKey }) { +function onChange({ resolved }) { const currentTab = tabs.value[tabs.value.length - 1]; + const route = resolved.route.path; if (resolved == null || 'redirect' in resolved.route) return; if (resolved.route.path === currentTab.path && deepEqual(resolved.props, currentTab.props)) return; - key.value = newKey + JSON.stringify(Object.fromEntries(resolved.props)); + const fullPath = router.getCurrentPath(); - if (tabs.value.some(tab => tab.key === key.value)) { + if (tabs.value.some(tab => tab.route === route && deepEqual(resolved.props, tab.props))) { const newTabs = []; for (const tab of tabs.value) { newTabs.push(tab); - if (tab.key === key.value) { + if (tab.route === route && deepEqual(resolved.props, tab.props)) { break; } } @@ -84,28 +86,44 @@ function onChange({ resolved, key: newKey }) { tabs.value = tabs.value.length >= prefer.s.numberOfPageCache ? [ ...tabs.value.slice(1), { - key: key.value, - path: router.getCurrentPath(), + key: fullPath, + path: fullPath, + route, component: resolved.route.component, props: resolved.props, }, ] : [...tabs.value, { - key: key.value, - path: router.getCurrentPath(), + key: fullPath, + path: fullPath, + route, component: resolved.route.component, props: resolved.props, }]; } +function onReplace({ path }) { + const currentTab = tabs.value[tabs.value.length - 1]; + console.log('replace', currentTab.path, path); + currentTab.path = path; + tabs.value = [...tabs.value.slice(0, tabs.value.length - 1), currentTab]; +} + +function mount() { + const currentTab = tabs.value[tabs.value.length - 1]; + tabs.value = [currentTab]; +} + function back() { - const last = tabs.value[tabs.value.length - 1]; - router.replace(last.path, last.key); + const prev = tabs.value[tabs.value.length - 2]; tabs.value = [...tabs.value.slice(0, tabs.value.length - 1)]; + router.replace(prev.path, prev.key); } +router.addListener('replace', onReplace); router.addListener('change', onChange); onBeforeUnmount(() => { + router.removeListener('replace', onReplace); router.removeListener('change', onChange); }); @@ -139,53 +157,74 @@ onBeforeUnmount(() => { } } -.root { +.tabs { position: relative; - height: 100%; - overflow: clip; -} - -.tabBg { - position: absolute; - z-index: 1; - top: 0; - left: 0; width: 100%; height: 100%; - background: #0003; - -webkit-backdrop-filter: var(--MI-blur, blur(3px)); - backdrop-filter: var(--MI-blur, blur(3px)); } .tab { - position: absolute; - top: 0; - left: 0; - height: 100%; - width: 100%; - box-sizing: border-box; + &:first-child { + position: relative; + width: 100%; + height: 100%; + + .tabFg { + position: relative; + width: 100%; + height: 100%; + } + + .tabContent { + position: relative; + width: 100%; + height: 100%; + } + } + + &:not(:first-child) { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + + .tabBg { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #0003; + -webkit-backdrop-filter: var(--MI-blur, blur(3px)); + backdrop-filter: var(--MI-blur, blur(3px)); + } - &:not(:nth-child(1)) { .tabFg { position: absolute; - z-index: 1; bottom: 0; left: 0; width: 100%; - height: calc(100% - 20px * var(--i)); + height: calc(100% - (10px + (20px * var(--i)))); + display: flex; + flex-direction: column; + } + + .tabContent { + flex: 1; + width: 100%; + height: 100%; + background: var(--MI_THEME-bg); } } } -.tabFg { - position: relative; - height: 100%; +.tabMenu { + margin-left: auto; background: var(--MI_THEME-bg); - border-radius: 16px 16px 0 0; - overflow: clip; } -.tabContent { - height: 100%; +.tabMenuButton { + padding: 10px; } -- cgit v1.2.3-freya From 62bf0d53d35f1e2a9b2740b88617334f2eadd219 Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Tue, 18 Mar 2025 22:21:28 +0900 Subject: 🎨 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/components/MkPageWindow.vue | 1 - packages/frontend/src/components/global/StackingRouterView.vue | 9 +++++++-- packages/frontend/src/pages/settings/index.vue | 2 +- packages/frontend/src/style.scss | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) (limited to 'packages/frontend/src/components/global/StackingRouterView.vue') diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue index eae2ccec4a..8a1a9c58d2 100644 --- a/packages/frontend/src/components/MkPageWindow.vue +++ b/packages/frontend/src/components/MkPageWindow.vue @@ -23,7 +23,6 @@ SPDX-License-Identifier: AGPL-3.0-only
-
diff --git a/packages/frontend/src/components/global/StackingRouterView.vue b/packages/frontend/src/components/global/StackingRouterView.vue index 71a91d4887..8a4afe7360 100644 --- a/packages/frontend/src/components/global/StackingRouterView.vue +++ b/packages/frontend/src/components/global/StackingRouterView.vue @@ -17,7 +17,8 @@ SPDX-License-Identifier: AGPL-3.0-only
- + +
@@ -164,6 +165,8 @@ onBeforeUnmount(() => { } .tab { + overflow: clip; + &:first-child { position: relative; width: 100%; @@ -221,10 +224,12 @@ onBeforeUnmount(() => { .tabMenu { margin-left: auto; + padding: 0 4px; background: var(--MI_THEME-bg); } .tabMenuButton { - padding: 10px; + padding: 8px; + font-size: 13px; } diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue index ea1b714aed..f6feaee453 100644 --- a/packages/frontend/src/pages/settings/index.vue +++ b/packages/frontend/src/pages/settings/index.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> diff --git a/packages/frontend/src/events.ts b/packages/frontend/src/events.ts index a74018223c..dfd3d4120c 100644 --- a/packages/frontend/src/events.ts +++ b/packages/frontend/src/events.ts @@ -10,5 +10,4 @@ export const globalEvents = new EventEmitter<{ themeChanging: () => void; themeChanged: () => void; clientNotification: (notification: Misskey.entities.Notification) => void; - requestClearPageCache: () => void; }>(); diff --git a/packages/frontend/src/lib/nirax.ts b/packages/frontend/src/lib/nirax.ts index cc20d497e6..8783874bc2 100644 --- a/packages/frontend/src/lib/nirax.ts +++ b/packages/frontend/src/lib/nirax.ts @@ -5,7 +5,7 @@ // NIRAX --- A lightweight router -import { onMounted, shallowRef } from 'vue'; +import { onBeforeUnmount, onMounted, shallowRef } from 'vue'; import { EventEmitter } from 'eventemitter3'; import type { Component, ShallowRef } from 'vue'; @@ -45,28 +45,28 @@ type ParsedPath = (string | { optional?: boolean; })[]; -export type RouterEvent = { +export type RouterEvents = { change: (ctx: { - beforePath: string; - path: string; - resolved: Resolved; + beforeFullPath: string; + fullPath: string; + resolved: PathResolvedResult; }) => void; replace: (ctx: { - path: string; + fullPath: string; }) => void; push: (ctx: { - beforePath: string; - path: string; + beforeFullPath: string; + fullPath: string; route: RouteDef | null; props: Map | null; }) => void; same: () => void; }; -export type Resolved = { +export type PathResolvedResult = { route: RouteDef; props: Map; - child?: Resolved; + child?: PathResolvedResult; redirected?: boolean; /** @internal */ @@ -102,39 +102,39 @@ function parsePath(path: string): ParsedPath { return res; } -export class Nirax extends EventEmitter { +export class Nirax extends EventEmitter { private routes: DEF; - public current: Resolved; - public currentRef: ShallowRef; + public current: PathResolvedResult; + public currentRef: ShallowRef; public currentRoute: ShallowRef; - private currentPath: string; + private currentFullPath: string; // /foo/bar?baz=qux#hash private isLoggedIn: boolean; private notFoundPageComponent: Component; private redirectCount = 0; - public navHook: ((path: string, flag?: RouterFlag) => boolean) | null = null; + public navHook: ((fullPath: string, flag?: RouterFlag) => boolean) | null = null; - constructor(routes: DEF, currentPath: Nirax['currentPath'], isLoggedIn: boolean, notFoundPageComponent: Component) { + constructor(routes: DEF, currentFullPath: Nirax['currentFullPath'], isLoggedIn: boolean, notFoundPageComponent: Component) { super(); this.routes = routes; - this.current = this.resolve(currentPath)!; + this.current = this.resolve(currentFullPath)!; this.currentRef = shallowRef(this.current); this.currentRoute = shallowRef(this.current.route); - this.currentPath = currentPath; + this.currentFullPath = currentFullPath; this.isLoggedIn = isLoggedIn; this.notFoundPageComponent = notFoundPageComponent; } public init() { - const res = this.navigate(this.currentPath, false); + const res = this.navigate(this.currentFullPath, false); this.emit('replace', { - path: res._parsedRoute.fullPath, + fullPath: res._parsedRoute.fullPath, }); } - public resolve(path: string): Resolved | null { - const fullPath = path; + public resolve(fullPath: string): PathResolvedResult | null { + let path = fullPath; let queryString: string | null = null; let hash: string | null = null; if (path[0] === '/') path = path.substring(1); @@ -153,7 +153,7 @@ export class Nirax extends EventEmitter { hash, }; - function check(routes: RouteDef[], _parts: string[]): Resolved | null { + function check(routes: RouteDef[], _parts: string[]): PathResolvedResult | null { forEachRouteLoop: for (const route of routes) { let parts = [..._parts]; @@ -256,14 +256,14 @@ export class Nirax extends EventEmitter { return check(this.routes, _parts); } - private navigate(path: string, emitChange = true, _redirected = false): Resolved { - const beforePath = this.currentPath; - this.currentPath = path; + private navigate(fullPath: string, emitChange = true, _redirected = false): PathResolvedResult { + const beforeFullPath = this.currentFullPath; + this.currentFullPath = fullPath; - const res = this.resolve(this.currentPath); + const res = this.resolve(this.currentFullPath); if (res == null) { - throw new Error('no route found for: ' + path); + throw new Error('no route found for: ' + fullPath); } if ('redirect' in res.route) { @@ -291,8 +291,8 @@ export class Nirax extends EventEmitter { if (emitChange && res.route.path !== '/:(*)') { this.emit('change', { - beforePath, - path, + beforeFullPath, + fullPath, resolved: res, }); } @@ -304,37 +304,45 @@ export class Nirax extends EventEmitter { }; } - public getCurrentPath() { - return this.currentPath; + public getCurrentFullPath() { + return this.currentFullPath; } - public push(path: string, flag?: RouterFlag) { - const beforePath = this.currentPath; - if (path === beforePath) { + public push(fullPath: string, flag?: RouterFlag) { + const beforeFullPath = this.currentFullPath; + if (fullPath === beforeFullPath) { this.emit('same'); return; } if (this.navHook) { - const cancel = this.navHook(path, flag); + const cancel = this.navHook(fullPath, flag); if (cancel) return; } - const res = this.navigate(path); + const res = this.navigate(fullPath); if (res.route.path === '/:(*)') { - location.href = path; + location.href = fullPath; } else { this.emit('push', { - beforePath, - path: res._parsedRoute.fullPath, + beforeFullPath, + fullPath: res._parsedRoute.fullPath, route: res.route, props: res.props, }); } } - public replace(path: string) { - const res = this.navigate(path); + public replace(fullPath: string) { + const res = this.navigate(fullPath); this.emit('replace', { - path: res._parsedRoute.fullPath, + fullPath: res._parsedRoute.fullPath, + }); + } + + public useListener(event: E, listener: L) { + this.addListener(event, listener); + + onBeforeUnmount(() => { + this.removeListener(event, listener); }); } } diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index e9b1ee8101..30b7cf9a86 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -169,7 +169,6 @@ import { langmap } from '@/utility/langmap.js'; import { definePage } from '@/page.js'; import { claimAchievement } from '@/utility/achievements.js'; import { store } from '@/store.js'; -import { globalEvents } from '@/events.js'; import MkInfo from '@/components/MkInfo.vue'; import MkTextarea from '@/components/MkTextarea.vue'; @@ -223,7 +222,6 @@ function saveFields() { os.apiWithDialog('i/update', { fields: fields.value.filter(field => field.name !== '' && field.value !== '').map(field => ({ name: field.name, value: field.value })), }); - globalEvents.emit('requestClearPageCache'); } function save() { @@ -249,7 +247,6 @@ function save() { text: i18n.ts.yourNameContainsProhibitedWordsDescription, }, }); - globalEvents.emit('requestClearPageCache'); claimAchievement('profileFilled'); if (profile.name === 'syuilo' || profile.name === 'しゅいろ') { claimAchievement('setNameToSyuilo'); @@ -281,7 +278,6 @@ function changeAvatar(ev) { }); $i.avatarId = i.avatarId; $i.avatarUrl = i.avatarUrl; - globalEvents.emit('requestClearPageCache'); claimAchievement('profileFilled'); }); } @@ -308,7 +304,6 @@ function changeBanner(ev) { }); $i.bannerId = i.bannerId; $i.bannerUrl = i.bannerUrl; - globalEvents.emit('requestClearPageCache'); }); } diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts index b5f59b30c1..dd70571d64 100644 --- a/packages/frontend/src/router.ts +++ b/packages/frontend/src/router.ts @@ -13,8 +13,8 @@ import { DI } from '@/di.js'; export type Router = Nirax; -export function createRouter(path: string): Router { - return new Nirax(ROUTE_DEF, path, !!$i, page(() => import('@/pages/not-found.vue'))); +export function createRouter(fullPath: string): Router { + return new Nirax(ROUTE_DEF, fullPath, !!$i, page(() => import('@/pages/not-found.vue'))); } export const mainRouter = createRouter(location.pathname + location.search + location.hash); @@ -24,23 +24,23 @@ window.addEventListener('popstate', (event) => { }); mainRouter.addListener('push', ctx => { - window.history.pushState({ }, '', ctx.path); + window.history.pushState({ }, '', ctx.fullPath); }); mainRouter.addListener('replace', ctx => { - window.history.replaceState({ }, '', ctx.path); + window.history.replaceState({ }, '', ctx.fullPath); }); mainRouter.addListener('change', ctx => { - console.log('mainRouter: change', ctx.path); + console.log('mainRouter: change', ctx.fullPath); analytics.page({ - path: ctx.path, - title: ctx.path, + path: ctx.fullPath, + title: ctx.fullPath, }); }); mainRouter.init(); export function useRouter(): Router { - return inject(DI.router, null) ?? mainRouter; + return inject(DI.router) ?? mainRouter; } diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue index fa63586ef7..2ef06726f9 100644 --- a/packages/frontend/src/ui/classic.vue +++ b/packages/frontend/src/ui/classic.vue @@ -112,7 +112,7 @@ function onContextmenu(ev: MouseEvent) { if (isLink(ev.target)) return; if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return; if (window.getSelection().toString() !== '') return; - const path = mainRouter.getCurrentPath(); + const path = mainRouter.getCurrentFullPath(); os.contextMenu([{ type: 'label', text: path, diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue index a6695de39d..133360972b 100644 --- a/packages/frontend/src/ui/universal.vue +++ b/packages/frontend/src/ui/universal.vue @@ -186,7 +186,7 @@ const onContextmenu = (ev) => { if (isLink(ev.target)) return; if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return; if (window.getSelection()?.toString() !== '') return; - const path = mainRouter.getCurrentPath(); + const path = mainRouter.getCurrentFullPath(); os.contextMenu([{ type: 'label', text: path, -- cgit v1.2.3-freya