Skip to content

Commit

Permalink
refactor(frontend): pageMetadataをDIしてみる
Browse files Browse the repository at this point in the history
  • Loading branch information
taiyme committed Feb 19, 2025
1 parent 533c11d commit 24311a4
Show file tree
Hide file tree
Showing 27 changed files with 657 additions and 739 deletions.
19 changes: 8 additions & 11 deletions packages/frontend/src/components/MkPageWindow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ SPDX-License-Identifier: AGPL-3.0-only
@closed="emit('closed')"
>
<template #header>
<template v-if="pageMetadata">
<i v-if="pageMetadata.icon" :class="pageMetadata.icon" style="margin-right: 0.5em;"></i>
<span>{{ pageMetadata.title }}</span>
<template v-if="routingPageMetadataRef">
<i v-if="routingPageMetadataRef.icon" :class="routingPageMetadataRef.icon" style="margin-right: 0.5em;"></i>
<span>{{ routingPageMetadataRef.title }}</span>
</template>
</template>

Expand All @@ -38,11 +38,14 @@ import { popout as _popout } from '@/scripts/popout.js';
import { copyText } from '@/scripts/tms/clipboard.js';
import { useScrollPositionManager } from '@/nirax.js';
import { i18n } from '@/i18n.js';
import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
import { useRoutingPageMetadata } from '@/scripts/page-metadata.js';
import { openingWindowsCount } from '@/os.js';
import { claimAchievement } from '@/scripts/achievements.js';
import { useRouterFactory } from '@/router/supplier.js';
import { mainRouter } from '@/router/main.js';
import { DI } from '@/di.js';

const { routingPageMetadataRef } = useRoutingPageMetadata();

const props = defineProps<{
initialPath: string;
Expand All @@ -56,7 +59,6 @@ const routerFactory = useRouterFactory();
const windowRouter = routerFactory(props.initialPath);

const contents = shallowRef<HTMLElement | null>(null);
const pageMetadata = ref<null | PageMetadata>(null);
const windowEl = shallowRef<InstanceType<typeof MkWindow>>();
const history = ref<{ path: string; key: string; }[]>([{
path: windowRouter.getCurrentPath(),
Expand Down Expand Up @@ -100,12 +102,7 @@ windowRouter.addListener('replace', ctx => {

windowRouter.init();

provide('router', windowRouter);
provideMetadataReceiver((metadataGetter) => {
const info = metadataGetter();
pageMetadata.value = info;
});
provideReactiveMetadata(pageMetadata);
provide(DI.router, windowRouter);
provide('shouldOmitHeaderTitle', true);
provide('shouldHeaderThin', true);
provide('forceSpacerMin', true);
Expand Down
24 changes: 12 additions & 12 deletions packages/frontend/src/components/global/MkPageHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div v-else-if="!thinRef && narrow && !hideTitleRef" :class="$style.buttonsLeft"/>

<template v-if="pageMetadata">
<template v-if="pageMetadataRef">
<div v-if="!hideTitleRef" :class="$style.titleContainer" @click="top">
<div v-if="pageMetadata.avatar" :class="$style.titleAvatarContainer">
<MkAvatar :class="$style.titleAvatar" :user="pageMetadata.avatar" indicator/>
<div v-if="pageMetadataRef.withUserAvatar" :class="$style.titleAvatarContainer">
<MkAvatar :class="$style.titleAvatar" :user="pageMetadataRef.withUserAvatar" indicator/>
</div>
<i v-else-if="pageMetadata.icon" :class="[$style.titleIcon, pageMetadata.icon]"></i>
<i v-else-if="pageMetadataRef.icon" :class="[$style.titleIcon, pageMetadataRef.icon]"></i>

<div :class="$style.title">
<MkUserName v-if="pageMetadata.userName" :user="pageMetadata.userName" :nowrap="true"/>
<div v-else-if="pageMetadata.title">{{ pageMetadata.title }}</div>
<div v-if="pageMetadata.subtitle" :class="$style.subtitle">
{{ pageMetadata.subtitle }}
<MkUserName v-if="pageMetadataRef.withUserName" :user="pageMetadataRef.withUserName" :nowrap="true"/>
<div v-else-if="pageMetadataRef.title">{{ pageMetadataRef.title }}</div>
<div v-if="pageMetadataRef.subtitle" :class="$style.subtitle">
{{ pageMetadataRef.subtitle }}
</div>
</div>
</div>
Expand Down Expand Up @@ -67,9 +67,10 @@ import { onMounted, onUnmounted, ref, inject, shallowRef, computed } from 'vue';
import { scrollToTop } from '@@/js/scroll.js';
import XTabs, { Tab } from './MkPageHeader.tabs.vue';
import type { PageHeaderItem } from '@/types/page-header.js';
import { injectReactiveMetadata, type PageMetadata } from '@/scripts/page-metadata.js';
import type { PageMetadata } from '@/scripts/page-metadata.js';
import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
import MkButton from '@/components/MkButton.vue';
import { DI } from '@/di.js';

const props = withDefaults(defineProps<{
overridePageMetadata?: PageMetadata | null;
Expand All @@ -90,12 +91,11 @@ const emit = defineEmits<{
(ev: 'update:tab', key: string): void;
}>();

const injectedPageMetadata = injectReactiveMetadata();
const pageMetadata = computed(() => props.overridePageMetadata ?? injectedPageMetadata.value);

const injectedPageMetadataRef = inject(DI.pageMetadata);
const injectedShouldOmitHeaderTitle = inject<boolean>('shouldOmitHeaderTitle', false);
const injectedShouldHeaderThin = inject<boolean>('shouldHeaderThin', false);

const pageMetadataRef = computed(() => props.overridePageMetadata ?? injectedPageMetadataRef?.value ?? null);
const hideTitleRef = computed(() => injectedShouldOmitHeaderTitle || props.hideTitle);
const thinRef = computed(() => injectedShouldHeaderThin || props.thin);

Expand Down
29 changes: 14 additions & 15 deletions packages/frontend/src/components/global/RouterView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,25 @@ SPDX-License-Identifier: AGPL-3.0-only

<script lang="ts" setup>
import { inject, onBeforeUnmount, provide, ref, shallowRef, computed, nextTick } from 'vue';
import { IRouter, Resolved } from '@/nirax.js';
import { IRouter, Resolved, RouterEvent } from '@/nirax.js';
import { defaultStore } from '@/store.js';
import { globalEvents } from '@/events.js';
import MkLoadingPage from '@/pages/_loading_.vue';
import { DI } from '@/di.js';

const props = defineProps<{
router?: IRouter;
nested?: boolean;
}>();

const router = props.router ?? inject<IRouter | null>('router', null);
const router = props.router ?? inject(DI.router, null);

if (router == null) {
throw new Error('no router provided');
}

const currentDepth = inject<number>('routerCurrentDepth', 0);
provide('routerCurrentDepth', currentDepth + 1);
const currentDepth = inject(DI.routerDepth, 0);
provide(DI.routerDepth, currentDepth + 1);

function resolveNested(current: Resolved, d = 0): Resolved | null {
if (!props.nested) return current;
Expand All @@ -58,25 +59,24 @@ const currentPageComponent = shallowRef('component' in current.route ? current.r
const currentPageProps = ref(current.props);
const key = ref(router.getCurrentKey() + JSON.stringify(Object.fromEntries(current.props)));

function onChange({ resolved, key: newKey }) {
const current = resolveNested(resolved);
if (current == null || 'redirect' in current.route) return;
currentPageComponent.value = current.route.component;
currentPageProps.value = current.props;
key.value = newKey + JSON.stringify(Object.fromEntries(current.props));
const onChange: RouterEvent['change'] = ({ resolved, key: newKey }) => {
const current_ = resolveNested(resolved);
if (current_ == null || 'redirect' in current_.route) return;
currentPageComponent.value = current_.route.component;
currentPageProps.value = current_.props;
key.value = newKey + JSON.stringify(Object.fromEntries(current_.props));

nextTick(() => {
// ページ遷移完了後に再びキャッシュを有効化
if (clearCacheRequested.value) {
clearCacheRequested.value = false;
}
});
}
};

router.addListener('change', onChange);

// #region キャッシュ制御

//#region キャッシュ制御
/**
* キャッシュクリアが有効になったら、全キャッシュをクリアする
*
Expand All @@ -92,8 +92,7 @@ globalEvents.on('requestClearPageCache', () => {
clearCacheRequested.value = true;
}
});

// #endregion
//#endregion

onBeforeUnmount(() => {
router.removeListener('change', onChange);
Expand Down
16 changes: 16 additions & 0 deletions packages/frontend/src/di.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/

import type { InjectionKey, ShallowRef } from 'vue';
import type { IRouter } from '@/nirax.js';
import type { PageMetadata, PageMetadataReceiver } from '@/scripts/page-metadata.js';

export const DI = Object.freeze({
router: Symbol() as InjectionKey<IRouter>,
routerFactory: Symbol() as InjectionKey<((path: string) => IRouter)>,
routerDepth: Symbol() as InjectionKey<number>,
pageMetadata: Symbol() as InjectionKey<Readonly<ShallowRef<PageMetadata>>>,
pageMetadataReceiver: Symbol() as InjectionKey<PageMetadataReceiver>,
});
54 changes: 31 additions & 23 deletions packages/frontend/src/nirax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

// NIRAX --- A lightweight router

import { Component, onMounted, shallowRef, ShallowRef } from 'vue';
import { type Component, type ShallowRef, onMounted, shallowRef } from 'vue';
import { EventEmitter } from 'eventemitter3';

function safeURIDecode(str: string): string {
Expand Down Expand Up @@ -60,7 +60,7 @@ export type RouterEvent = {
beforePath: string;
path: string;
route: RouteDef | null;
props: Map<string, string> | null;
props: Map<string, string | boolean> | null;
key: string;
}) => void;
same: () => void;
Expand All @@ -83,6 +83,7 @@ export type Resolved = {
function parsePath(path: string): ParsedPath {
const res = [] as ParsedPath;

// eslint-disable-next-line no-param-reassign
path = path.substring(1);

for (const part of path.split('/')) {
Expand Down Expand Up @@ -131,12 +132,12 @@ export interface IRouter extends EventEmitter<RouterEvent> {

/** @see EventEmitter */
listeners<T extends EventEmitter.EventNames<RouterEvent>>(
event: T
event: T,
): Array<EventEmitter.EventListener<RouterEvent, T>>;

/** @see EventEmitter */
listenerCount(
event: EventEmitter.EventNames<RouterEvent>
event: EventEmitter.EventNames<RouterEvent>,
): number;

/** @see EventEmitter */
Expand All @@ -149,42 +150,42 @@ export interface IRouter extends EventEmitter<RouterEvent> {
on<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn: EventEmitter.EventListener<RouterEvent, T>,
context?: any
context?: any, // eslint-disable-line @typescript-eslint/no-explicit-any
): this;

/** @see EventEmitter */
addListener<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn: EventEmitter.EventListener<RouterEvent, T>,
context?: any
context?: any, // eslint-disable-line @typescript-eslint/no-explicit-any
): this;

/** @see EventEmitter */
once<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn: EventEmitter.EventListener<RouterEvent, T>,
context?: any
context?: any, // eslint-disable-line @typescript-eslint/no-explicit-any
): this;

/** @see EventEmitter */
removeListener<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn?: EventEmitter.EventListener<RouterEvent, T>,
context?: any,
once?: boolean | undefined
context?: any, // eslint-disable-line @typescript-eslint/no-explicit-any
once?: boolean | undefined,
): this;

/** @see EventEmitter */
off<T extends EventEmitter.EventNames<RouterEvent>>(
event: T,
fn?: EventEmitter.EventListener<RouterEvent, T>,
context?: any,
once?: boolean | undefined
context?: any, // eslint-disable-line @typescript-eslint/no-explicit-any
once?: boolean | undefined,
): this;

/** @see EventEmitter */
removeAllListeners(
event?: EventEmitter.EventNames<RouterEvent>
event?: EventEmitter.EventNames<RouterEvent>,
): this;
}

Expand All @@ -205,7 +206,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
super();

this.routes = routes;
this.current = this.resolve(currentPath)!;
this.current = this.resolve(currentPath)!; // eslint-disable-line @typescript-eslint/no-non-null-assertion
this.currentRef = shallowRef(this.current);
this.currentRoute = shallowRef(this.current.route);
this.currentPath = currentPath;
Expand All @@ -225,14 +226,16 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
const fullPath = path;
let queryString: string | null = null;
let hash: string | null = null;
if (path[0] === '/') path = path.substring(1);
if (path[0] === '/') {
path = path.substring(1); // eslint-disable-line no-param-reassign
}
if (path.includes('#')) {
hash = path.substring(path.indexOf('#') + 1);
path = path.substring(0, path.indexOf('#'));
path = path.substring(0, path.indexOf('#')); // eslint-disable-line no-param-reassign
}
if (path.includes('?')) {
queryString = path.substring(path.indexOf('?') + 1);
path = path.substring(0, path.indexOf('?'));
path = path.substring(0, path.indexOf('?')); // eslint-disable-line no-param-reassign
}

const _parsedRoute = {
Expand All @@ -247,7 +250,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
forEachRouteLoop:
for (const route of routes) {
let parts = [..._parts];
const props = new Map<string, string>();
const props = new Map<string, string | boolean>();

pathMatchLoop:
for (const p of parsePath(route.path)) {
Expand All @@ -258,6 +261,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
continue forEachRouteLoop;
}
} else {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (parts[0] == null && !p.optional) {
continue forEachRouteLoop;
}
Expand All @@ -269,8 +273,10 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
break pathMatchLoop;
} else {
if (p.startsWith) {
if (parts[0] == null || !parts[0].startsWith(p.startsWith)) continue forEachRouteLoop;

// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (parts[0] == null || !parts[0].startsWith(p.startsWith)) {
continue forEachRouteLoop;
}
props.set(p.name, safeURIDecode(parts[0].substring(p.startsWith.length)));
parts.shift();
} else {
Expand Down Expand Up @@ -303,8 +309,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
}

if (route.query != null && queryString != null) {
const queryObject = [...new URLSearchParams(queryString).entries()]
.reduce((obj, entry) => ({ ...obj, [entry[0]]: entry[1] }), {});
const queryObject = Object.fromEntries(new URLSearchParams(queryString));

for (const q in route.query) {
const as = route.query[q];
Expand Down Expand Up @@ -376,7 +381,9 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
}

const isSamePath = beforePath === path;
if (isSamePath && key == null) key = this.currentKey;
if (isSamePath && key == null) {
key = this.currentKey; // eslint-disable-line no-param-reassign
}
this.current = res;
this.currentRef.value = res;
this.currentRoute.value = res.route;
Expand Down Expand Up @@ -454,7 +461,8 @@ export function useScrollPositionManager(getScrollContainer: () => HTMLElement |
const scrollPos = scrollPosStore.get(ctx.key) ?? 0;
scrollContainer.scroll({ top: scrollPos, behavior: 'instant' });
if (scrollPos !== 0) {
window.setTimeout(() => { // 遷移直後はタイミングによってはコンポーネントが復元し切ってない可能性も考えられるため少し時間を空けて再度スクロール
// 遷移直後はタイミングによってはコンポーネントが復元し切ってない可能性も考えられるため少し時間を空けて再度スクロール
window.setTimeout(() => {
scrollContainer.scroll({ top: scrollPos, behavior: 'instant' });
}, 100);
}
Expand Down
Loading

0 comments on commit 24311a4

Please sign in to comment.