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` 图片输出正常。
|
- 已新增 `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-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-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 没有真实图片落盘时必须失败。
|
- smoke/Jenkins/远程调试卡点继续按已固化规则处理:同一卡点第二次尝试前必须改变可验证变量;Jenkins 查询 URL 含方括号时用 `curl -g` 或改查 Jenkins home;线上图片 smoke 必须查询 `commandId`、拉回图片、展示图片、查日志并清理本轮临时目录;PowerShell 双引号 here-string 中不要写 JS `${...}` 模板字面量,避免被 PowerShell 提前插值;smoke 没有真实图片落盘时必须失败。
|
||||||
|
|
||||||
### Phase 6:策略 policy 和时间/档线规则
|
### 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 { globalDefaultServer } from '@/qqbot/plugins/bangDream/tsugu/runtime/config';
|
||||||
import { drawList } from './list-frame';
|
import { drawList } from './list-frame';
|
||||||
import { drawDottedLine } from '@/qqbot/plugins/bangDream/tsugu/canvas/dotted-line';
|
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 { 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列表。
|
* 在图片布局层中绘制歌曲In列表。
|
||||||
@ -32,21 +38,40 @@ export async function drawSongInList(
|
|||||||
const server = getServerByPriority(song.publishedAt, displayedServerList);
|
const server = getServerByPriority(song.publishedAt, displayedServerList);
|
||||||
const songImage = resizeImage({
|
const songImage = resizeImage({
|
||||||
image: await song.getSongJacketImage(),
|
image: await song.getSongJacketImage(),
|
||||||
widthMax: 80,
|
widthMax: BANGDREAM_SONG_LIST_SPEC.item.jacketSourceWidthMax,
|
||||||
heightMax: 80,
|
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');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.drawImage(songImage, 50, 5, 65, 65);
|
ctx.drawImage(
|
||||||
|
songImage,
|
||||||
|
layout.jacketX,
|
||||||
|
layout.jacketY,
|
||||||
|
layout.jacketWidth,
|
||||||
|
layout.jacketHeight,
|
||||||
|
);
|
||||||
//id
|
//id
|
||||||
const idImage = drawText({
|
const idImage = drawText({
|
||||||
text: song.songId.toString(),
|
text: song.songId.toString(),
|
||||||
textSize: 23,
|
textSize: layout.textSize,
|
||||||
lineHeight: 37.5,
|
lineHeight: layout.textLineHeight,
|
||||||
maxWidth: BANGDREAM_RENDER_THEME.layout.contentWidth,
|
maxWidth: layout.textMaxWidth,
|
||||||
});
|
});
|
||||||
ctx.drawImage(idImage, 0, 0);
|
ctx.drawImage(idImage, layout.idTextX, layout.idTextY);
|
||||||
//曲名与乐队名
|
//曲名与乐队名
|
||||||
let fullText = `${song.musicTitle[server]}`;
|
let fullText = `${song.musicTitle[server]}`;
|
||||||
if (!text) {
|
if (!text) {
|
||||||
@ -58,22 +83,13 @@ export async function drawSongInList(
|
|||||||
}
|
}
|
||||||
const textImage = drawText({
|
const textImage = drawText({
|
||||||
text: fullText,
|
text: fullText,
|
||||||
textSize: 23,
|
textSize: layout.textSize,
|
||||||
lineHeight: 37.5,
|
lineHeight: layout.textLineHeight,
|
||||||
maxWidth: BANGDREAM_RENDER_THEME.layout.contentWidth,
|
maxWidth: layout.textMaxWidth,
|
||||||
});
|
});
|
||||||
ctx.drawImage(textImage, 120, 0);
|
ctx.drawImage(textImage, layout.titleTextX, layout.titleTextY);
|
||||||
|
|
||||||
//难度
|
ctx.drawImage(difficultyImage, layout.difficultyX, layout.difficultyY);
|
||||||
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,
|
|
||||||
);
|
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,13 +108,18 @@ export async function drawSongListInList(
|
|||||||
text?: string,
|
text?: string,
|
||||||
displayedServerList: Server[] = globalDefaultServer,
|
displayedServerList: Server[] = globalDefaultServer,
|
||||||
): Promise<Canvas> {
|
): Promise<Canvas> {
|
||||||
const height: number = 75 * songs.length + 10 * (songs.length - 1);
|
const contentWidth = getSongListContentWidth();
|
||||||
const canvas = new Canvas(760, height);
|
const height = getSongListCanvasHeight(songs.length);
|
||||||
|
const canvas = new Canvas(contentWidth, height);
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
const x = 0;
|
const x = 0;
|
||||||
let y = 0;
|
let y = 0;
|
||||||
const views: Canvas[] = [];
|
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++) {
|
for (let i = 0; i < songs.length; i++) {
|
||||||
views.push(
|
views.push(
|
||||||
resizeImage({
|
resizeImage({
|
||||||
@ -108,7 +129,7 @@ export async function drawSongListInList(
|
|||||||
text,
|
text,
|
||||||
displayedServerList,
|
displayedServerList,
|
||||||
),
|
),
|
||||||
widthMax: 760,
|
widthMax: contentWidth,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
views.push(line);
|
views.push(line);
|
||||||
@ -119,10 +140,10 @@ export async function drawSongListInList(
|
|||||||
y += views[i].height;
|
y += views[i].height;
|
||||||
}
|
}
|
||||||
return await drawList({
|
return await drawList({
|
||||||
key: '歌榜歌曲',
|
key: BANGDREAM_SONG_LIST_SPEC.list.key,
|
||||||
content: [canvas],
|
content: [canvas],
|
||||||
textSize: canvas.height,
|
textSize: canvas.height,
|
||||||
lineHeight: canvas.height + 20,
|
lineHeight: getSongListFrameLineHeight(canvas.height),
|
||||||
spacing: 0,
|
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