[优化] 添加 web 端的 xbox 翻页支持 (#639)
* xbox for web supported * disable debug log * vue导入ts文件问题 * 修复 package.json 依赖问题 * 更改web.yml上游 * Bump web v3.26.022400
This commit is contained in:
@@ -12,7 +12,7 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
UPSTREAM_REPOSITORY: gedoor/legado
|
||||
UPSTREAM_REPOSITORY: HapeLee/legado-with-MD3
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
.el-link[data-v-085627fb]{padding:4px}.el-text[data-v-085627fb]{padding-top:20px}[data-v-d8dae8d3] .el-checkbox__label{flex:1;display:flex;justify-content:space-between;align-items:center}.error[data-v-d8dae8d3]{border-color:var(--el-color-error)!important;color:var(--el-color-error)!important;--el-checkbox-checked-text-color: var(--el-color-error);--el-checkbox-checked-bg-color: var(--el-color-error);--el-checkbox-checked-input-border-color: var(--el-color-error)}.edit[data-v-d8dae8d3]{border-color:var(--el-color-dark)!important}.tool[data-v-258cd99b]{display:flex;margin:4px 0;justify-content:center}#source-list[data-v-258cd99b]{margin-top:6px;height:calc(100vh - 119px)}#source-list[data-v-258cd99b] .el-checkbox{margin-bottom:4px;width:100%}[data-v-3ac68c8a] #debug-text{height:calc(100vh - 86px)}[data-v-f62d9369] .el-input{width:100%}[data-v-f62d9369] #source-json{height:calc(100vh - 50px)}[data-v-fd81540f] .el-tabs__header{margin-bottom:5px}.flex-space-between[data-v-096adf6d]{display:flex;justify-content:space-between;align-items:baseline}.flex-column-center[data-v-096adf6d]{display:flex;flex-direction:column;justify-content:center}.menu>.el-button[data-v-096adf6d]{margin:4px;padding:1em;width:6em}.hotkeys-item .title[data-v-096adf6d]{width:5em;display:flex;justify-content:flex-end;margin-right:1em}.hotkeys-item .hotkeys-item__content[data-v-096adf6d]{display:flex;flex-wrap:wrap;flex:1}.hotkeys-item .hotkeys-item__content div[data-v-096adf6d]{margin-bottom:1em}.hotkeys-item .hotkeys-item__content span[data-v-096adf6d]{margin:.5em}[data-v-c07c5146] .el-tab-pane{height:calc(100vh - 55px);padding-top:15px;padding-right:5px;overflow-y:auto}[data-v-c07c5146] .el-tabs__header{margin:0}kbd{align-items:center;background:#7d7d7d1a;border-radius:3px;border:0;padding:4px 5px;font-weight:700;box-shadow:inset 0 -2px #cdcde6,inset 0 0 1px 1px #fff,0 1px 2px 1px #1e235a66}code{border-radius:4px;padding:.15rem .5rem;background-color:var(--el-fill-color-light);transition:color .25s,background-color .5s;font-size:14px}body{padding:0;margin:0}.el-tabs__header{position:sticky;top:0;z-index:2}.editor[data-v-f2c47af3]{display:flex;height:100vh;overflow:hidden}.editor .left[data-v-f2c47af3]{flex:1;margin-left:20px}.editor .right[data-v-f2c47af3]{flex:1;width:360px;margin-right:20px}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
.el-link[data-v-f5093f50]{padding:4px}.el-text[data-v-f5093f50]{padding-top:20px}[data-v-008e87f8] .el-checkbox__label{flex:1;display:flex;justify-content:space-between;align-items:center}.error[data-v-008e87f8]{border-color:var(--el-color-error)!important;color:var(--el-color-error)!important;--el-checkbox-checked-text-color: var(--el-color-error);--el-checkbox-checked-bg-color: var(--el-color-error);--el-checkbox-checked-input-border-color: var(--el-color-error)}.edit[data-v-008e87f8]{border-color:var(--el-color-dark)!important}.tool[data-v-f42bab7c]{display:flex;margin:4px 0;justify-content:center}#source-list[data-v-f42bab7c]{margin-top:6px;height:calc(100vh - 119px)}#source-list[data-v-f42bab7c] .el-checkbox{margin-bottom:4px;width:100%}[data-v-29708ead] #debug-text{height:calc(100vh - 86px)}[data-v-1d793504] .el-input{width:100%}[data-v-1d793504] #source-json{height:calc(100vh - 50px)}[data-v-7b77cf24] .el-tabs__header{margin-bottom:5px}.flex-space-between[data-v-5658ded6]{display:flex;justify-content:space-between;align-items:baseline}.flex-column-center[data-v-5658ded6]{display:flex;flex-direction:column;justify-content:center}.menu>.el-button[data-v-5658ded6]{margin:4px;padding:1em;width:6em}.hotkeys-item .title[data-v-5658ded6]{width:5em;display:flex;justify-content:flex-end;margin-right:1em}.hotkeys-item .hotkeys-item__content[data-v-5658ded6]{display:flex;flex-wrap:wrap;flex:1}.hotkeys-item .hotkeys-item__content div[data-v-5658ded6]{margin-bottom:1em}.hotkeys-item .hotkeys-item__content span[data-v-5658ded6]{margin:.5em}[data-v-ad9f3194] .el-tab-pane{height:calc(100vh - 55px);padding-top:15px;padding-right:5px;overflow-y:auto}[data-v-ad9f3194] .el-tabs__header{margin:0}kbd{align-items:center;background:#7d7d7d1a;border-radius:3px;border:0;padding:4px 5px;font-weight:700;box-shadow:inset 0 -2px #cdcde6,inset 0 0 1px 1px #fff,0 1px 2px 1px #1e235a66}code{border-radius:4px;padding:.15rem .5rem;background-color:var(--el-fill-color-light);transition:color .25s,background-color .5s;font-size:14px}body{padding:0;margin:0}.el-tabs__header{position:sticky;top:0;z-index:2}.editor[data-v-d184d43a]{display:flex;height:100vh;overflow:hidden}.editor .left[data-v-d184d43a]{flex:1;margin-left:20px}.editor .right[data-v-d184d43a]{flex:1;width:360px;margin-right:20px}
|
||||
@@ -0,0 +1 @@
|
||||
import{ak as i,N as g,a9 as c,I as d,al as f,u as m}from"./vendor-DQqeohSF.js";const L=(s,t,l=i)=>{const a=d(!1);let r=null;const o=()=>a.value=!1,n=()=>a.value=!0;g(a,e=>{if(!e)return r==null?void 0:r.close();r=f.service({target:m(s),spinner:l,text:t,lock:!0,background:"rgba(0, 0, 0, 0)"})});const u=e=>{if(!(e instanceof Promise))throw TypeError("loadingWrapper argument must be Promise");return n(),e.finally(o)};return c(()=>{o()}),{isLoading:a,showLoading:n,closeLoading:o,loadingWrapper:u}};export{L as u};
|
||||
@@ -1 +0,0 @@
|
||||
import{aj as l,B as g,L as c,a8 as d,ak as f,u as m}from"./vendor-C3IVQyCm.js";const L=(s,t,u=l)=>{const a=g(!1);let r=null;const o=()=>a.value=!1,n=()=>a.value=!0;c(a,e=>{if(!e)return r==null?void 0:r.close();r=f.service({target:m(s),spinner:u,text:t,lock:!0,background:"rgba(0, 0, 0, 0)"})});const i=e=>{if(!(e instanceof Promise))throw TypeError("loadingWrapper argument must be Promise");return n(),e.finally(o)};return d(()=>{o()}),{isLoading:a,showLoading:n,closeLoading:o,loadingWrapper:i}};export{L as u};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,17 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh" class="">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="./favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<script crossorigin src="./assets/index-D8bE419P.js" type="module"></script>
|
||||
<link crossorigin href="./assets/vendor-C3IVQyCm.js" rel="modulepreload">
|
||||
<link crossorigin href="./assets/vendor-Dt8JbPMC.css" rel="stylesheet">
|
||||
<link crossorigin href="./assets/index-srCojK1V.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
</body>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh" class="">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="./favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<script type="module" crossorigin src="./assets/index-DYOzJbC3.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="./assets/vendor-DQqeohSF.js">
|
||||
<link rel="stylesheet" crossorigin href="./assets/vendor-CXe1BRiH.css">
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-CrxHVQK7.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>␍
|
||||
</body>
|
||||
</html>
|
||||
@@ -19,12 +19,12 @@
|
||||
"dependencies": {
|
||||
"@element-plus/icons-svg": "^2.3.1",
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@vueuse/core": "^13.8.0",
|
||||
"@vueuse/core": "^11.1.0",
|
||||
"@vueuse/shared": "^11.1.0",
|
||||
"axios": "^1.7.7",
|
||||
"element-plus": "^2.8.5",
|
||||
"element-plus": "2.8.5",
|
||||
"hotkeys-js": "^3.13.7",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia": "^2.2.4",
|
||||
"vue": "^3.5.12",
|
||||
"vue-router": "^4.4.5",
|
||||
"vue3-virtual-scroll-list": "^0.2.1"
|
||||
@@ -32,11 +32,11 @@
|
||||
"devDependencies": {
|
||||
"sass-embedded": "^1.83.1",
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/node": "^24.3.0",
|
||||
"@types/node": "^20.16.11",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"@vue/eslint-config-prettier": "^10.0.0",
|
||||
"@vue/eslint-config-typescript": "^14.0.1",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"eslint": "^9.12.0",
|
||||
"eslint-plugin-vue": "^9.29.0",
|
||||
"npm-run-all2": "^6.2.3",
|
||||
@@ -46,6 +46,6 @@
|
||||
"unplugin-icons": "^0.19.3",
|
||||
"unplugin-vue-components": "^0.27.4",
|
||||
"vite": "^5.4.8",
|
||||
"vue-tsc": "^3.0.6"
|
||||
"vue-tsc": "^2.1.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+1319
-1265
File diff suppressed because it is too large
Load Diff
@@ -69,7 +69,6 @@ import API from '@api'
|
||||
import { CircleCheckFilled, Edit } from '@element-plus/icons-vue'
|
||||
import hotkeys from 'hotkeys-js'
|
||||
import { getSourceName, isInvaildSource, normalizeSource } from '../utils/souce'
|
||||
import { getSourceName, isInvaildSource } from '../utils/souce'
|
||||
|
||||
const store = useSourceStore()
|
||||
const pull = () => {
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
// =====================================================
|
||||
// 类型定义
|
||||
// =====================================================
|
||||
|
||||
interface GamepadConfig {
|
||||
DEBUG: boolean;
|
||||
AXIS_THRESHOLD: number;
|
||||
AXIS_COOLDOWN: number;
|
||||
DPAD_INDEX: {
|
||||
UP: number;
|
||||
DOWN: number;
|
||||
LEFT: number;
|
||||
RIGHT: number;
|
||||
};
|
||||
}
|
||||
|
||||
interface DPadState {
|
||||
up: boolean;
|
||||
down: boolean;
|
||||
left: boolean;
|
||||
right: boolean;
|
||||
}
|
||||
|
||||
interface GlobalState {
|
||||
lastAxisTime: number;
|
||||
lastAxisDirection: number;
|
||||
dpadPressed: DPadState;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 配置区域
|
||||
// =====================================================
|
||||
|
||||
const CONFIG: GamepadConfig = {
|
||||
DEBUG: false, // 日志总开关
|
||||
AXIS_THRESHOLD: 0.7, // 摇杆触发阈值
|
||||
AXIS_COOLDOWN: 300, // 摇杆触发冷却时间 (ms)
|
||||
DPAD_INDEX: { // Xbox 标准映射
|
||||
UP: 12,
|
||||
DOWN: 13,
|
||||
LEFT: 14,
|
||||
RIGHT: 15
|
||||
}
|
||||
};
|
||||
|
||||
// =====================================================
|
||||
// 日志工具函数
|
||||
// =====================================================
|
||||
|
||||
function log(...args: any[]): void {
|
||||
if (!CONFIG.DEBUG) return;
|
||||
console.log(...args);
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 状态记录
|
||||
// =====================================================
|
||||
|
||||
const state: GlobalState = {
|
||||
lastAxisTime: 0,
|
||||
lastAxisDirection: 0,
|
||||
dpadPressed: {
|
||||
up: false,
|
||||
down: false,
|
||||
left: false,
|
||||
right: false
|
||||
}
|
||||
};
|
||||
|
||||
// =====================================================
|
||||
// 通用工具函数
|
||||
// =====================================================
|
||||
|
||||
/**
|
||||
* 平滑翻页函数
|
||||
* @param direction 1 = 向下, -1 = 向上
|
||||
*/
|
||||
function scrollPage(direction: number): void {
|
||||
const offset = window.innerHeight - 110;
|
||||
const distance = direction === 1 ? offset : -offset;
|
||||
|
||||
log(direction === 1 ? "🎮 翻页:向下" : "🎮 翻页:向上");
|
||||
|
||||
window.scrollBy({
|
||||
top: distance,
|
||||
behavior: "smooth"
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 章节切换函数
|
||||
* @param direction 1 = 下一章, -1 = 上一章
|
||||
*/
|
||||
function switchChapter(direction: number): void {
|
||||
const buttons = document.querySelectorAll<HTMLElement>(".read-bar .tool-icon");
|
||||
|
||||
if (buttons.length < 2) return;
|
||||
|
||||
if (direction === 1) {
|
||||
log("🎮 切换章节:下一章");
|
||||
buttons[1].click();
|
||||
} else {
|
||||
log("🎮 切换章节:上一章");
|
||||
buttons[0].click();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 边沿检测函数
|
||||
*/
|
||||
function isPressedOnce(current: boolean, previous: boolean): boolean {
|
||||
return current && !previous;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 摇杆处理
|
||||
// =====================================================
|
||||
|
||||
function handleAxis(gp: Gamepad, now: number): void {
|
||||
const axisY = gp.axes[1] || 0;
|
||||
|
||||
if (Math.abs(axisY) <= CONFIG.AXIS_THRESHOLD) {
|
||||
state.lastAxisDirection = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const direction = axisY > 0 ? 1 : -1;
|
||||
const cooldownPassed = now - state.lastAxisTime > CONFIG.AXIS_COOLDOWN;
|
||||
const directionChanged = direction !== state.lastAxisDirection;
|
||||
|
||||
if (cooldownPassed || directionChanged) {
|
||||
state.lastAxisTime = now;
|
||||
state.lastAxisDirection = direction;
|
||||
log(`🎮 摇杆触发 | axisY=${axisY.toFixed(2)}`);
|
||||
scrollPage(direction);
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// DPad 处理
|
||||
// =====================================================
|
||||
|
||||
function handleDPad(gp: Gamepad): void {
|
||||
const indexes = CONFIG.DPAD_INDEX;
|
||||
|
||||
const current: DPadState = {
|
||||
up: gp.buttons[indexes.UP]?.pressed || false,
|
||||
down: gp.buttons[indexes.DOWN]?.pressed || false,
|
||||
left: gp.buttons[indexes.LEFT]?.pressed || false,
|
||||
right: gp.buttons[indexes.RIGHT]?.pressed || false
|
||||
};
|
||||
|
||||
if (isPressedOnce(current.up, state.dpadPressed.up)) {
|
||||
log("🎮 DPad 上");
|
||||
scrollPage(-1);
|
||||
}
|
||||
if (isPressedOnce(current.down, state.dpadPressed.down)) {
|
||||
log("🎮 DPad 下");
|
||||
scrollPage(1);
|
||||
}
|
||||
if (isPressedOnce(current.left, state.dpadPressed.left)) {
|
||||
log("🎮 DPad 左");
|
||||
switchChapter(-1);
|
||||
}
|
||||
if (isPressedOnce(current.right, state.dpadPressed.right)) {
|
||||
log("🎮 DPad 右");
|
||||
switchChapter(1);
|
||||
}
|
||||
|
||||
state.dpadPressed = current;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 主手柄处理入口
|
||||
// =====================================================
|
||||
|
||||
function handleGamepad(gp: Gamepad | null): void {
|
||||
if (!gp) return;
|
||||
const now = performance.now();
|
||||
handleAxis(gp, now);
|
||||
handleDPad(gp);
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 主循环
|
||||
// =====================================================
|
||||
|
||||
let running = false;
|
||||
|
||||
function gamepadLoop(): void {
|
||||
const gamepads = navigator.getGamepads?.() || [];
|
||||
|
||||
for (const gp of gamepads) {
|
||||
if (gp) handleGamepad(gp);
|
||||
}
|
||||
|
||||
requestAnimationFrame(gamepadLoop);
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 连接 / 断开 事件
|
||||
// =====================================================
|
||||
|
||||
window.addEventListener("gamepadconnected", (e: GamepadEvent) => {
|
||||
log("🎮 手柄已连接:", e.gamepad.id);
|
||||
if (!running) {
|
||||
running = true;
|
||||
requestAnimationFrame(gamepadLoop);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("gamepaddisconnected", (e: GamepadEvent) => {
|
||||
log("🎮 手柄已断开:", e.gamepad.id);
|
||||
});
|
||||
|
||||
export function initXboxGamepad(): void {
|
||||
if (!running) {
|
||||
running = true;
|
||||
requestAnimationFrame(gamepadLoop);
|
||||
}
|
||||
}
|
||||
@@ -113,6 +113,7 @@ import API from '@api'
|
||||
import { useLoading } from '@/hooks/loading'
|
||||
import { useThrottleFn } from '@vueuse/shared'
|
||||
import { isNullOrBlank } from '@/utils/utils'
|
||||
import { initXboxGamepad } from '@/utils/xboxGamepad'
|
||||
|
||||
const content = ref()
|
||||
// loading spinner
|
||||
@@ -519,6 +520,7 @@ onMounted(async () => {
|
||||
document.title = (name as string) + ' | ' + chapters[chapterIndex].title
|
||||
}),
|
||||
)
|
||||
initXboxGamepad()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
|
||||
Reference in New Issue
Block a user