refactor: 收口 BangDream 歌曲列表规格
This commit is contained in:
parent
e1ace51d6e
commit
b360a005e7
@ -387,6 +387,7 @@ export interface TsuguHook {
|
||||
- 已新增 `event-stage-spec.spec.ts`,覆盖试炼歌曲行尺寸、类型顶部文字规格、换列判断和按列高拆分算法;本地生成 `event-stage-split-310*.jpg`、`event-stage-split-310-meta*.jpg`,验证 `/查试炼 310` 与 `/查试炼 310 -m` 图片输出正常。
|
||||
- 已新增 `render-blocks/list-player-card-icon-spec.ts`,收口玩家详情主卡组展示顺序、默认行高、文本字号比例、卡牌间距比例和卡牌图标可见性标记;`list-player-card-icon-list.ts` 改为消费 spec,主卡组渲染顺序和输出结构保持不变;`list-player-card-icon-spec.spec.ts` 覆盖历史卡牌顺序、缺失条目跳过和字号/间距计算,本地 `/查玩家 26591455 jp` 图片 smoke 输出非空。
|
||||
- 已新增 `render-blocks/list-card-icon-spec.ts`,收口通用卡牌图标列表默认行高、文本字号比例、卡牌间距比例、默认可见性标记和历史排序比较器;`list-card-icon-list.ts` 改为消费 spec,卡牌图标列表排序和输出结构保持不变;`list-card-icon-spec.spec.ts` 覆盖稀有度排序、优先卡牌类型排序、普通卡按 ID 排序和字号/间距计算,本地 `/查卡 472` 图片 smoke 输出非空。
|
||||
- 已新增 `render-blocks/list-song-spec.ts`,收口歌曲列表单行封面、文本、难度块位置、列表内容宽度、分割线高度和外层行高计算;`list-song.ts` 改为消费 spec,歌曲列表绘制顺序和输出结构保持不变;`list-song-spec.spec.ts` 覆盖单行尺寸、难度块垂直居中和歌曲组列表尺寸,本地 `/查曲 136` 图片 smoke 输出非空。
|
||||
- smoke/Jenkins/远程调试卡点继续按已固化规则处理:同一卡点第二次尝试前必须改变可验证变量;Jenkins 查询 URL 含方括号时用 `curl -g` 或改查 Jenkins home;线上图片 smoke 必须查询 `commandId`、拉回图片、展示图片、查日志并清理本轮临时目录;PowerShell 双引号 here-string 中不要写 JS `${...}` 模板字面量,避免被 PowerShell 提前插值;smoke 没有真实图片落盘时必须失败。
|
||||
|
||||
### Phase 6:策略 policy 和时间/档线规则
|
||||
|
||||
@ -0,0 +1,91 @@
|
||||
import { getListFrameTextMaxWidth } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-frame-spec';
|
||||
import { BANGDREAM_RENDER_THEME } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/theme';
|
||||
|
||||
interface ImageLike {
|
||||
height: number;
|
||||
width: number;
|
||||
}
|
||||
|
||||
export const BANGDREAM_SONG_LIST_SPEC = {
|
||||
item: {
|
||||
canvasHeight: 75,
|
||||
difficultyHeight: 45,
|
||||
difficultySpacing: 10,
|
||||
idTextX: 0,
|
||||
idTextY: 0,
|
||||
jacketHeight: 65,
|
||||
jacketSourceHeightMax: 80,
|
||||
jacketSourceWidthMax: 80,
|
||||
jacketWidth: 65,
|
||||
jacketX: 50,
|
||||
jacketY: 5,
|
||||
textLineHeight: 37.5,
|
||||
textSize: 23,
|
||||
titleTextX: 120,
|
||||
titleTextY: 0,
|
||||
},
|
||||
list: {
|
||||
key: '歌榜歌曲',
|
||||
lineHeightPadding: 20,
|
||||
separatorHeight: 10,
|
||||
spacing: 0,
|
||||
},
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 计算歌曲列表单行布局。
|
||||
*
|
||||
* @param difficultyImage - 难度图尺寸。
|
||||
* @param contentWidth - 内容宽度。
|
||||
*/
|
||||
export function createSongInListLayout(
|
||||
difficultyImage: ImageLike,
|
||||
contentWidth: number = BANGDREAM_RENDER_THEME.layout.contentWidth,
|
||||
) {
|
||||
const item = BANGDREAM_SONG_LIST_SPEC.item;
|
||||
return {
|
||||
canvasHeight: item.canvasHeight,
|
||||
canvasWidth: contentWidth,
|
||||
difficultyX: contentWidth - difficultyImage.width,
|
||||
difficultyY: item.canvasHeight / 2 - difficultyImage.height / 2,
|
||||
idTextX: item.idTextX,
|
||||
idTextY: item.idTextY,
|
||||
jacketHeight: item.jacketHeight,
|
||||
jacketSourceHeightMax: item.jacketSourceHeightMax,
|
||||
jacketSourceWidthMax: item.jacketSourceWidthMax,
|
||||
jacketWidth: item.jacketWidth,
|
||||
jacketX: item.jacketX,
|
||||
jacketY: item.jacketY,
|
||||
textLineHeight: item.textLineHeight,
|
||||
textMaxWidth: contentWidth,
|
||||
textSize: item.textSize,
|
||||
titleTextX: item.titleTextX,
|
||||
titleTextY: item.titleTextY,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取歌曲列表内容宽度。
|
||||
*/
|
||||
export function getSongListContentWidth() {
|
||||
return getListFrameTextMaxWidth(BANGDREAM_RENDER_THEME.layout.contentWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算歌曲列表组画布高度。
|
||||
*
|
||||
* @param songCount - 歌曲数量。
|
||||
*/
|
||||
export function getSongListCanvasHeight(songCount: number) {
|
||||
const { item, list } = BANGDREAM_SONG_LIST_SPEC;
|
||||
return item.canvasHeight * songCount + list.separatorHeight * (songCount - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算歌曲列表外层行高。
|
||||
*
|
||||
* @param canvasHeight - 歌曲列表组画布高度。
|
||||
*/
|
||||
export function getSongListFrameLineHeight(canvasHeight: number) {
|
||||
return canvasHeight + BANGDREAM_SONG_LIST_SPEC.list.lineHeightPadding;
|
||||
}
|
||||
@ -11,8 +11,14 @@ import { drawDifficultyList, drawDifficulty } from './list-difficulty';
|
||||
import { globalDefaultServer } from '@/qqbot/plugins/bangDream/tsugu/runtime/config';
|
||||
import { drawList } from './list-frame';
|
||||
import { drawDottedLine } from '@/qqbot/plugins/bangDream/tsugu/canvas/dotted-line';
|
||||
import { BANGDREAM_RENDER_THEME } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/theme';
|
||||
import { createHorizontalSeparatorSpec } from '@/qqbot/plugins/bangDream/tsugu/render-blocks/layout-spec';
|
||||
import {
|
||||
BANGDREAM_SONG_LIST_SPEC,
|
||||
createSongInListLayout,
|
||||
getSongListCanvasHeight,
|
||||
getSongListContentWidth,
|
||||
getSongListFrameLineHeight,
|
||||
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-song-spec';
|
||||
|
||||
/**
|
||||
* 在图片布局层中绘制歌曲In列表。
|
||||
@ -32,21 +38,40 @@ export async function drawSongInList(
|
||||
const server = getServerByPriority(song.publishedAt, displayedServerList);
|
||||
const songImage = resizeImage({
|
||||
image: await song.getSongJacketImage(),
|
||||
widthMax: 80,
|
||||
heightMax: 80,
|
||||
widthMax: BANGDREAM_SONG_LIST_SPEC.item.jacketSourceWidthMax,
|
||||
heightMax: BANGDREAM_SONG_LIST_SPEC.item.jacketSourceHeightMax,
|
||||
});
|
||||
|
||||
const canvas = new Canvas(BANGDREAM_RENDER_THEME.layout.contentWidth, 75);
|
||||
const difficultyImage =
|
||||
difficulty == undefined
|
||||
? drawDifficultyList(
|
||||
song,
|
||||
BANGDREAM_SONG_LIST_SPEC.item.difficultyHeight,
|
||||
BANGDREAM_SONG_LIST_SPEC.item.difficultySpacing,
|
||||
)
|
||||
: drawDifficulty(
|
||||
difficulty,
|
||||
song.difficulty[difficulty].playLevel,
|
||||
BANGDREAM_SONG_LIST_SPEC.item.difficultyHeight,
|
||||
);
|
||||
const layout = createSongInListLayout(difficultyImage);
|
||||
const canvas = new Canvas(layout.canvasWidth, layout.canvasHeight);
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(songImage, 50, 5, 65, 65);
|
||||
ctx.drawImage(
|
||||
songImage,
|
||||
layout.jacketX,
|
||||
layout.jacketY,
|
||||
layout.jacketWidth,
|
||||
layout.jacketHeight,
|
||||
);
|
||||
//id
|
||||
const idImage = drawText({
|
||||
text: song.songId.toString(),
|
||||
textSize: 23,
|
||||
lineHeight: 37.5,
|
||||
maxWidth: BANGDREAM_RENDER_THEME.layout.contentWidth,
|
||||
textSize: layout.textSize,
|
||||
lineHeight: layout.textLineHeight,
|
||||
maxWidth: layout.textMaxWidth,
|
||||
});
|
||||
ctx.drawImage(idImage, 0, 0);
|
||||
ctx.drawImage(idImage, layout.idTextX, layout.idTextY);
|
||||
//曲名与乐队名
|
||||
let fullText = `${song.musicTitle[server]}`;
|
||||
if (!text) {
|
||||
@ -58,22 +83,13 @@ export async function drawSongInList(
|
||||
}
|
||||
const textImage = drawText({
|
||||
text: fullText,
|
||||
textSize: 23,
|
||||
lineHeight: 37.5,
|
||||
maxWidth: BANGDREAM_RENDER_THEME.layout.contentWidth,
|
||||
textSize: layout.textSize,
|
||||
lineHeight: layout.textLineHeight,
|
||||
maxWidth: layout.textMaxWidth,
|
||||
});
|
||||
ctx.drawImage(textImage, 120, 0);
|
||||
ctx.drawImage(textImage, layout.titleTextX, layout.titleTextY);
|
||||
|
||||
//难度
|
||||
const difficultyImage =
|
||||
difficulty == undefined
|
||||
? drawDifficultyList(song, 45, 10)
|
||||
: drawDifficulty(difficulty, song.difficulty[difficulty].playLevel, 45);
|
||||
ctx.drawImage(
|
||||
difficultyImage,
|
||||
BANGDREAM_RENDER_THEME.layout.contentWidth - difficultyImage.width,
|
||||
75 / 2 - difficultyImage.height / 2,
|
||||
);
|
||||
ctx.drawImage(difficultyImage, layout.difficultyX, layout.difficultyY);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
@ -92,13 +108,18 @@ export async function drawSongListInList(
|
||||
text?: string,
|
||||
displayedServerList: Server[] = globalDefaultServer,
|
||||
): Promise<Canvas> {
|
||||
const height: number = 75 * songs.length + 10 * (songs.length - 1);
|
||||
const canvas = new Canvas(760, height);
|
||||
const contentWidth = getSongListContentWidth();
|
||||
const height = getSongListCanvasHeight(songs.length);
|
||||
const canvas = new Canvas(contentWidth, height);
|
||||
const ctx = canvas.getContext('2d');
|
||||
const x = 0;
|
||||
let y = 0;
|
||||
const views: Canvas[] = [];
|
||||
const line = drawDottedLine(createHorizontalSeparatorSpec({ height: 10 }));
|
||||
const line = drawDottedLine(
|
||||
createHorizontalSeparatorSpec({
|
||||
height: BANGDREAM_SONG_LIST_SPEC.list.separatorHeight,
|
||||
}),
|
||||
);
|
||||
for (let i = 0; i < songs.length; i++) {
|
||||
views.push(
|
||||
resizeImage({
|
||||
@ -108,7 +129,7 @@ export async function drawSongListInList(
|
||||
text,
|
||||
displayedServerList,
|
||||
),
|
||||
widthMax: 760,
|
||||
widthMax: contentWidth,
|
||||
}),
|
||||
);
|
||||
views.push(line);
|
||||
@ -119,10 +140,10 @@ export async function drawSongListInList(
|
||||
y += views[i].height;
|
||||
}
|
||||
return await drawList({
|
||||
key: '歌榜歌曲',
|
||||
key: BANGDREAM_SONG_LIST_SPEC.list.key,
|
||||
content: [canvas],
|
||||
textSize: canvas.height,
|
||||
lineHeight: canvas.height + 20,
|
||||
spacing: 0,
|
||||
lineHeight: getSongListFrameLineHeight(canvas.height),
|
||||
spacing: BANGDREAM_SONG_LIST_SPEC.list.spacing,
|
||||
});
|
||||
}
|
||||
|
||||
46
test/qqbot/plugins/bangDream/tsugu/list-song-spec.spec.ts
Normal file
46
test/qqbot/plugins/bangDream/tsugu/list-song-spec.spec.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import {
|
||||
BANGDREAM_SONG_LIST_SPEC,
|
||||
createSongInListLayout,
|
||||
getSongListCanvasHeight,
|
||||
getSongListContentWidth,
|
||||
getSongListFrameLineHeight,
|
||||
} from '@/qqbot/plugins/bangDream/tsugu/render-blocks/list-song-spec';
|
||||
|
||||
describe('BangDream song list spec', () => {
|
||||
it('keeps song list item dimensions stable', () => {
|
||||
const layout = createSongInListLayout({ height: 45, width: 270 });
|
||||
|
||||
expect(layout).toEqual({
|
||||
canvasHeight: 75,
|
||||
canvasWidth: 800,
|
||||
difficultyX: 530,
|
||||
difficultyY: 15,
|
||||
idTextX: 0,
|
||||
idTextY: 0,
|
||||
jacketHeight: 65,
|
||||
jacketSourceHeightMax: 80,
|
||||
jacketSourceWidthMax: 80,
|
||||
jacketWidth: 65,
|
||||
jacketX: 50,
|
||||
jacketY: 5,
|
||||
textLineHeight: 37.5,
|
||||
textMaxWidth: 800,
|
||||
textSize: 23,
|
||||
titleTextX: 120,
|
||||
titleTextY: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('centers the difficulty image vertically for the row height', () => {
|
||||
expect(createSongInListLayout({ height: 30, width: 100 }).difficultyY).toBe(
|
||||
22.5,
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps song list group dimensions stable', () => {
|
||||
expect(BANGDREAM_SONG_LIST_SPEC.list.key).toBe('歌榜歌曲');
|
||||
expect(getSongListContentWidth()).toBe(760);
|
||||
expect(getSongListCanvasHeight(3)).toBe(245);
|
||||
expect(getSongListFrameLineHeight(245)).toBe(265);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user