From 92891786246cecf1aaddef3179b57479976edd53 Mon Sep 17 00:00:00 2001 From: Cleo John Date: Tue, 14 Feb 2023 20:16:35 +0100 Subject: [PATCH] experiment: replace nirax with vue router? --- packages/client/package.json | 3 +- packages/client/src/nirax.ts | 287 ---------------------------------- packages/client/src/router.ts | 55 +------ pnpm-lock.yaml | 30 ++-- 4 files changed, 24 insertions(+), 351 deletions(-) delete mode 100644 packages/client/src/nirax.ts diff --git a/packages/client/package.json b/packages/client/package.json index fe6288406b..550bbf605f 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -9,7 +9,8 @@ "dependencies": { "@khmyznikov/pwa-install": "^0.2.0", "chartjs-chart-matrix": "^2.0.1", - "gsap": "^3.11.4" + "gsap": "^3.11.4", + "vue-router": "v4" }, "devDependencies": { "@discordapp/twemoji": "14.0.2", diff --git a/packages/client/src/nirax.ts b/packages/client/src/nirax.ts deleted file mode 100644 index 7e481f1484..0000000000 --- a/packages/client/src/nirax.ts +++ /dev/null @@ -1,287 +0,0 @@ -// NIRAX --- A lightweight router - -import { EventEmitter } from "eventemitter3"; -import { Ref, Component, ref, shallowRef, ShallowRef } from "vue"; -import { pleaseLogin } from "@/scripts/please-login"; -import { safeURIDecode } from "@/scripts/safe-uri-decode"; - -type RouteDef = { - path: string; - component: Component; - query?: Record; - loginRequired?: boolean; - name?: string; - hash?: string; - globalCacheKey?: string; - children?: RouteDef[]; -}; - -type ParsedPath = ( - | string - | { - name: string; - startsWith?: string; - wildcard?: boolean; - optional?: boolean; - } -)[]; - -export type Resolved = { - route: RouteDef; - props: Map; - child?: Resolved; -}; - -function parsePath(path: string): ParsedPath { - const res = [] as ParsedPath; - - path = path.substring(1); - - for (const part of path.split("/")) { - if (part.includes(":")) { - const prefix = part.substring(0, part.indexOf(":")); - const placeholder = part.substring(part.indexOf(":") + 1); - const wildcard = placeholder.includes("(*)"); - const optional = placeholder.endsWith("?"); - res.push({ - name: placeholder.replace("(*)", "").replace("?", ""), - startsWith: prefix !== "" ? prefix : undefined, - wildcard, - optional, - }); - } else if (part.length !== 0) { - res.push(part); - } - } - - return res; -} - -export class Router extends EventEmitter<{ - change: (ctx: { - beforePath: string; - path: string; - resolved: Resolved; - key: string; - }) => void; - replace: (ctx: { - path: string; - key: string; - }) => void; - push: (ctx: { - beforePath: string; - path: string; - route: RouteDef | null; - props: Map | null; - key: string; - }) => void; - same: () => void; -}> { - private routes: RouteDef[]; - public current: Resolved; - public currentRef: ShallowRef = shallowRef(); - public currentRoute: ShallowRef = shallowRef(); - private currentPath: string; - private currentKey = Date.now().toString(); - - public navHook: ((path: string, flag?: any) => boolean) | null = null; - - constructor(routes: Router["routes"], currentPath: Router["currentPath"]) { - super(); - - this.routes = routes; - this.currentPath = currentPath; - this.navigate(currentPath, null, false); - } - - public resolve(path: string): Resolved | null { - let queryString: string | null = null; - let hash: string | null = null; - if (path[0] === "/") path = path.substring(1); - if (path.includes("#")) { - hash = path.substring(path.indexOf("#") + 1); - path = path.substring(0, path.indexOf("#")); - } - if (path.includes("?")) { - queryString = path.substring(path.indexOf("?") + 1); - path = path.substring(0, path.indexOf("?")); - } - - if (_DEV_) console.log("Routing: ", path, queryString); - - function check(routes: RouteDef[], _parts: string[]): Resolved | null { - forEachRouteLoop: for (const route of routes) { - let parts = [..._parts]; - const props = new Map(); - - pathMatchLoop: for (const p of parsePath(route.path)) { - if (typeof p === "string") { - if (p === parts[0]) { - parts.shift(); - } else { - continue forEachRouteLoop; - } - } else { - if (parts[0] == null && !p.optional) { - continue forEachRouteLoop; - } - if (p.wildcard) { - if (parts.length !== 0) { - props.set(p.name, safeURIDecode(parts.join("/"))); - parts = []; - } - break pathMatchLoop; - } else { - if (p.startsWith) { - 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 { - if (parts[0]) { - props.set(p.name, safeURIDecode(parts[0])); - } - parts.shift(); - } - } - } - } - - if (parts.length === 0) { - if (route.children) { - const child = check(route.children, []); - if (child) { - return { - route, - props, - child, - }; - } else { - continue forEachRouteLoop; - } - } - - if (route.hash != null && hash != null) { - props.set(route.hash, safeURIDecode(hash)); - } - - if (route.query != null && queryString != null) { - const queryObject = [ - ...new URLSearchParams(queryString).entries(), - ].reduce((obj, entry) => ({ ...obj, [entry[0]]: entry[1] }), {}); - - for (const q in route.query) { - const as = route.query[q]; - if (queryObject[q]) { - props.set(as, safeURIDecode(queryObject[q])); - } - } - } - - return { - route, - props, - }; - } else { - if (route.children) { - const child = check(route.children, parts); - if (child) { - return { - route, - props, - child, - }; - } else { - } - } else { - } - } - } - - return null; - } - - const _parts = path.split("/").filter((part) => part.length !== 0); - - return check(this.routes, _parts); - } - - private navigate( - path: string, - key: string | null | undefined, - emitChange = true, - ) { - const beforePath = this.currentPath; - this.currentPath = path; - - const res = this.resolve(this.currentPath); - - if (res == null) { - throw new Error(`no route found for: ${path}`); - } - - if (res.route.loginRequired) { - pleaseLogin("/"); - } - - const isSamePath = beforePath === path; - if (isSamePath && key == null) key = this.currentKey; - this.current = res; - this.currentRef.value = res; - this.currentRoute.value = res.route; - this.currentKey = res.route.globalCacheKey ?? key ?? path; - - if (emitChange) { - this.emit("change", { - beforePath, - path, - resolved: res, - key: this.currentKey, - }); - } - - return res; - } - - public getCurrentPath() { - return this.currentPath; - } - - public getCurrentKey() { - return this.currentKey; - } - - public push(path: string, flag?: any) { - const beforePath = this.currentPath; - if (path === beforePath) { - this.emit("same"); - return; - } - if (this.navHook) { - const cancel = this.navHook(path, flag); - if (cancel) return; - } - const res = this.navigate(path, null); - this.emit("push", { - beforePath, - path, - route: res.route, - props: res.props, - key: this.currentKey, - }); - } - - public replace(path: string, key?: string | null, emitEvent = true) { - this.navigate(path, key); - if (emitEvent) { - this.emit("replace", { - path, - key: this.currentKey, - }); - } - } -} diff --git a/packages/client/src/router.ts b/packages/client/src/router.ts index 48aad0820f..00625db793 100644 --- a/packages/client/src/router.ts +++ b/packages/client/src/router.ts @@ -1,10 +1,9 @@ import { AsyncComponentLoader, defineAsyncComponent, inject } from "vue"; -import { Router } from "@/nirax"; +import VueRouter from "vue-router" import { $i, iAmModerator } from "@/account"; import MkLoading from "@/pages/_loading_.vue"; import MkError from "@/pages/_error_.vue"; import { api } from "@/os"; -import { ui } from "@/config"; function getGuestTimelineStatus() { api("meta", { @@ -637,19 +636,13 @@ export const routes = [ }, ]; -export const mainRouter = new Router( +export const mainRouter = VueRouter.createRouter({ routes, - location.pathname + location.search + location.hash, -); - -window.history.replaceState( - { key: mainRouter.getCurrentKey() }, - "", - location.href, -); + history: VueRouter.createWebHistory(), +}); // TODO: このファイルでスクロール位置も管理する設計だとdeckに対応できないのでなんとかする -// スクロール位置取得+スクロール位置設定関数をprovideする感じでも良いかも +// スクロール位置取得+スクロール位置設定関数をprovideする感じでも良いかも <- indeed, we figured that out too ~ CLEO const scrollPosStore = new Map(); @@ -657,40 +650,6 @@ window.setInterval(() => { scrollPosStore.set(window.history.state?.key, window.scrollY); }, 1000); -mainRouter.addListener("push", (ctx) => { - window.history.pushState({ key: ctx.key }, "", ctx.path); - const scrollPos = scrollPosStore.get(ctx.key) ?? 0; - window.scroll({ top: scrollPos, behavior: "instant" }); - if (scrollPos !== 0) { - window.setTimeout(() => { - // 遷移直後はタイミングによってはコンポーネントが復元し切ってない可能性も考えられるため少し時間を空けて再度スクロール - window.scroll({ top: scrollPos, behavior: "instant" }); - }, 100); - } -}); - -mainRouter.addListener("replace", (ctx) => { - window.history.replaceState({ key: ctx.key }, "", ctx.path); -}); - -mainRouter.addListener("same", () => { - window.scroll({ top: 0, behavior: "smooth" }); -}); - -window.addEventListener("popstate", (event) => { - mainRouter.replace( - location.pathname + location.search + location.hash, - event.state?.key, - false, - ); - const scrollPos = scrollPosStore.get(event.state?.key) ?? 0; - window.scroll({ top: scrollPos, behavior: "instant" }); - window.setTimeout(() => { - // 遷移直後はタイミングによってはコンポーネントが復元し切ってない可能性も考えられるため少し時間を空けて再度スクロール - window.scroll({ top: scrollPos, behavior: "instant" }); - }, 100); -}); - -export function useRouter(): Router { - return inject("router", null) ?? mainRouter; +export function useRouter(): VueRouter.Router { + return inject("router", null) ?? mainRouter; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 06ec19ecae..7b73800e7a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -463,11 +463,13 @@ importers: vue-isyourpasswordsafe: ^2.0.0 vue-plyr: ^7.0.0 vue-prism-editor: 2.0.0-alpha.2 + vue-router: v4 vuedraggable: 4.1.0 dependencies: '@khmyznikov/pwa-install': 0.2.0 chartjs-chart-matrix: 2.0.1_chart.js@4.1.1 gsap: 3.11.4 + vue-router: 4.1.6_vue@3.2.45 devDependencies: '@discordapp/twemoji': 14.0.2 '@rollup/plugin-alias': 3.1.9_rollup@3.9.1 @@ -2631,14 +2633,12 @@ packages: '@vue/shared': 3.2.45 estree-walker: 2.0.2 source-map: 0.6.1 - dev: true /@vue/compiler-dom/3.2.45: resolution: {integrity: sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==} dependencies: '@vue/compiler-core': 3.2.45 '@vue/shared': 3.2.45 - dev: true /@vue/compiler-sfc/2.7.14: resolution: {integrity: sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==} @@ -2661,14 +2661,16 @@ packages: magic-string: 0.25.9 postcss: 8.4.21 source-map: 0.6.1 - dev: true /@vue/compiler-ssr/3.2.45: resolution: {integrity: sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==} dependencies: '@vue/compiler-dom': 3.2.45 '@vue/shared': 3.2.45 - dev: true + + /@vue/devtools-api/6.5.0: + resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==} + dev: false /@vue/reactivity-transform/3.2.45: resolution: {integrity: sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==} @@ -2678,20 +2680,17 @@ packages: '@vue/shared': 3.2.45 estree-walker: 2.0.2 magic-string: 0.25.9 - dev: true /@vue/reactivity/3.2.45: resolution: {integrity: sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==} dependencies: '@vue/shared': 3.2.45 - dev: true /@vue/runtime-core/3.2.45: resolution: {integrity: sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==} dependencies: '@vue/reactivity': 3.2.45 '@vue/shared': 3.2.45 - dev: true /@vue/runtime-dom/3.2.45: resolution: {integrity: sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==} @@ -2699,7 +2698,6 @@ packages: '@vue/runtime-core': 3.2.45 '@vue/shared': 3.2.45 csstype: 2.6.21 - dev: true /@vue/server-renderer/3.2.45_vue@3.2.45: resolution: {integrity: sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==} @@ -2709,11 +2707,9 @@ packages: '@vue/compiler-ssr': 3.2.45 '@vue/shared': 3.2.45 vue: 3.2.45 - dev: true /@vue/shared/3.2.45: resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==} - dev: true /@webassemblyjs/ast/1.11.1: resolution: {integrity: sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==} @@ -4999,7 +4995,6 @@ packages: /csstype/2.6.21: resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==} - dev: true /csstype/3.1.1: resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} @@ -5889,7 +5884,6 @@ packages: /estree-walker/2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - dev: true /esutils/2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} @@ -8805,7 +8799,6 @@ packages: resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} dependencies: sourcemap-codec: 1.4.8 - dev: true /mailcheck/1.1.1: resolution: {integrity: sha512-3WjL8+ZDouZwKlyJBMp/4LeziLFXgleOdsYu87piGcMLqhBzCsy2QFdbtAwv757TFC/rtqd738fgJw1tFQCSgA==} @@ -11622,7 +11615,6 @@ packages: /sourcemap-codec/1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} deprecated: Please use @jridgewell/sourcemap-codec instead - dev: true /sparkles/1.0.1: resolution: {integrity: sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==} @@ -12952,6 +12944,15 @@ packages: vue: 3.2.45 dev: true + /vue-router/4.1.6_vue@3.2.45: + resolution: {integrity: sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==} + peerDependencies: + vue: ^3.2.0 + dependencies: + '@vue/devtools-api': 6.5.0 + vue: 3.2.45 + dev: false + /vue/2.7.14: resolution: {integrity: sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==} dependencies: @@ -12967,7 +12968,6 @@ packages: '@vue/runtime-dom': 3.2.45 '@vue/server-renderer': 3.2.45_vue@3.2.45 '@vue/shared': 3.2.45 - dev: true /vuedraggable/4.1.0_vue@3.2.45: resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}