왕조 연감에 해당 기수의 지도 레이아웃을 적용한다
This commit is contained in:
@@ -66,8 +66,10 @@ const resolveMapName = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loadMapLayout = async (scenario: string, options: MapLayoutLoaderOptions = {}): Promise<MapLayout> => {
|
export const loadMapLayoutByName = async (
|
||||||
const mapName = await resolveMapName(scenario, options.loadScenario ?? loadScenarioDefinitionById);
|
mapName: string,
|
||||||
|
options: MapLayoutLoaderOptions = {}
|
||||||
|
): Promise<MapLayout> => {
|
||||||
const useCache = !options.loadScenario && !options.loadMap && !options.loadRegionMap;
|
const useCache = !options.loadScenario && !options.loadMap && !options.loadRegionMap;
|
||||||
const cached = useCache ? layoutCache.get(mapName) : undefined;
|
const cached = useCache ? layoutCache.get(mapName) : undefined;
|
||||||
if (cached) {
|
if (cached) {
|
||||||
@@ -98,3 +100,8 @@ export const loadMapLayout = async (scenario: string, options: MapLayoutLoaderOp
|
|||||||
}
|
}
|
||||||
return layout;
|
return layout;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const loadMapLayout = async (scenario: string, options: MapLayoutLoaderOptions = {}): Promise<MapLayout> => {
|
||||||
|
const mapName = await resolveMapName(scenario, options.loadScenario ?? loadScenarioDefinitionById);
|
||||||
|
return loadMapLayoutByName(mapName, options);
|
||||||
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { asRecord, isRecord } from '@sammo-ts/common';
|
|||||||
import { LogCategory, LogScope } from '@sammo-ts/logic';
|
import { LogCategory, LogScope } from '@sammo-ts/logic';
|
||||||
|
|
||||||
import type { GameApiContext } from '../../context.js';
|
import type { GameApiContext } from '../../context.js';
|
||||||
|
import { loadMapLayout, loadMapLayoutByName } from '../../maps/mapLayout.js';
|
||||||
import { loadPublicMap, type BaseMapResult } from '../../maps/worldMap.js';
|
import { loadPublicMap, type BaseMapResult } from '../../maps/worldMap.js';
|
||||||
import {
|
import {
|
||||||
formatGeneralAccessLimitMessage,
|
formatGeneralAccessLimitMessage,
|
||||||
@@ -339,11 +340,23 @@ export const yearbookRouter = router({
|
|||||||
const firstYearMonth = firstRow ? joinYearMonth(firstRow.year, firstRow.month) : fallbackYearMonth;
|
const firstYearMonth = firstRow ? joinYearMonth(firstRow.year, firstRow.month) : fallbackYearMonth;
|
||||||
const lastYearMonth = lastRow ? joinYearMonth(lastRow.year, lastRow.month) : fallbackYearMonth;
|
const lastYearMonth = lastRow ? joinYearMonth(lastRow.year, lastRow.month) : fallbackYearMonth;
|
||||||
const selectedYearMonth = target.isCurrentProfile ? currentYearMonth : lastYearMonth;
|
const selectedYearMonth = target.isCurrentProfile ? currentYearMonth : lastYearMonth;
|
||||||
|
// Ref v_history.php resolves an archived generation's map from ng_games.map.
|
||||||
|
const mapLayout = target.isCurrentProfile
|
||||||
|
? await loadMapLayout(ctx.profile.scenario)
|
||||||
|
: await loadMapLayoutByName(
|
||||||
|
(
|
||||||
|
await ctx.db.gameHistory.findUnique({
|
||||||
|
where: { serverId: target.archiveKey },
|
||||||
|
select: { map: true },
|
||||||
|
})
|
||||||
|
)?.map || 'che'
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
firstYearMonth,
|
firstYearMonth,
|
||||||
lastYearMonth,
|
lastYearMonth,
|
||||||
currentYearMonth: selectedYearMonth,
|
currentYearMonth: selectedYearMonth,
|
||||||
|
mapLayout,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
getHistory: authedProcedure
|
getHistory: authedProcedure
|
||||||
|
|||||||
@@ -142,6 +142,10 @@ const buildContext = (
|
|||||||
meta: options.worldMeta ?? { serverId: currentServerId },
|
meta: options.worldMeta ?? { serverId: currentServerId },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
gameHistory: {
|
||||||
|
findUnique: async ({ where }: { where: { serverId: string } }) =>
|
||||||
|
where.serverId === archiveServerId ? { map: 'miniche' } : null,
|
||||||
|
},
|
||||||
yearbookHistory: {
|
yearbookHistory: {
|
||||||
findFirst: async (args: {
|
findFirst: async (args: {
|
||||||
where: { profileName: string; year?: number; month?: number };
|
where: { profileName: string; year?: number; month?: number };
|
||||||
@@ -187,11 +191,13 @@ describe('historical yearbook access from dynasty', () => {
|
|||||||
const caller = appRouter.createCaller(buildContext(authFor('owner-a')));
|
const caller = appRouter.createCaller(buildContext(authFor('owner-a')));
|
||||||
const result = await caller.yearbook.getRange({ serverID: archiveServerId });
|
const result = await caller.yearbook.getRange({ serverID: archiveServerId });
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toMatchObject({
|
||||||
firstYearMonth: 200 * 12,
|
firstYearMonth: 200 * 12,
|
||||||
lastYearMonth: 200 * 12 + 1,
|
lastYearMonth: 200 * 12 + 1,
|
||||||
currentYearMonth: 200 * 12 + 1,
|
currentYearMonth: 200 * 12 + 1,
|
||||||
|
mapLayout: { mapName: 'miniche' },
|
||||||
});
|
});
|
||||||
|
expect(result.mapLayout.cityList.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns the same archived public history for distinct general owners', async () => {
|
it('returns the same archived public history for distinct general owners', async () => {
|
||||||
@@ -249,20 +255,22 @@ describe('historical yearbook access from dynasty', () => {
|
|||||||
|
|
||||||
expect(canonical).toEqual(alias);
|
expect(canonical).toEqual(alias);
|
||||||
expect(alias).toEqual(omitted);
|
expect(alias).toEqual(omitted);
|
||||||
expect(canonical).toEqual({
|
expect(canonical).toMatchObject({
|
||||||
firstYearMonth: 219 * 12 + 11,
|
firstYearMonth: 219 * 12 + 11,
|
||||||
lastYearMonth: 219 * 12 + 11,
|
lastYearMonth: 219 * 12 + 11,
|
||||||
currentYearMonth: 220 * 12,
|
currentYearMonth: 220 * 12,
|
||||||
|
mapLayout: { mapName: 'che' },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reads imported history under the short profile ID when world metadata has no server ID', async () => {
|
it('reads imported history under the short profile ID when world metadata has no server ID', async () => {
|
||||||
const caller = appRouter.createCaller(buildContext(authFor('owner-a'), { worldMeta: {} }));
|
const caller = appRouter.createCaller(buildContext(authFor('owner-a'), { worldMeta: {} }));
|
||||||
|
|
||||||
await expect(caller.yearbook.getRange()).resolves.toEqual({
|
await expect(caller.yearbook.getRange()).resolves.toMatchObject({
|
||||||
firstYearMonth: 219 * 12 + 10,
|
firstYearMonth: 219 * 12 + 10,
|
||||||
lastYearMonth: 219 * 12 + 10,
|
lastYearMonth: 219 * 12 + 10,
|
||||||
currentYearMonth: 220 * 12,
|
currentYearMonth: 220 * 12,
|
||||||
|
mapLayout: { mapName: 'che' },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { writeFile } from 'node:fs/promises';
|
||||||
import { expect, test, type Page, type Route } from '@playwright/test';
|
import { expect, test, type Page, type Route } from '@playwright/test';
|
||||||
|
|
||||||
import { gameProfile, gameTrpcRoute } from './gameTestPaths.js';
|
import { gameProfile, gameTrpcRoute } from './gameTestPaths.js';
|
||||||
@@ -156,7 +157,12 @@ const installArchiveViews = async (page: Page) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (operation === 'yearbook.getRange') {
|
if (operation === 'yearbook.getRange') {
|
||||||
return response({ firstYearMonth: 22001, lastYearMonth: 22001, currentYearMonth: 22001 });
|
return response({
|
||||||
|
firstYearMonth: 22001,
|
||||||
|
lastYearMonth: 22001,
|
||||||
|
currentYearMonth: 22001,
|
||||||
|
mapLayout: { mapName: 'miniche', cityList: [], regionMap: {}, levelMap: {} },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (operation === 'public.getMapLayout') {
|
if (operation === 'public.getMapLayout') {
|
||||||
return response({ mapName: 'che', cityList: [], regionMap: {}, levelMap: {} });
|
return response({ mapName: 'che', cityList: [], regionMap: {}, levelMap: {} });
|
||||||
@@ -293,3 +299,48 @@ test('연감 국가 라벨은 밝은 배경에 검정, 어두운 배경에 흰
|
|||||||
await expect(labels.filter({ hasText: '명국' })).toHaveCSS('color', 'rgb(0, 0, 0)');
|
await expect(labels.filter({ hasText: '명국' })).toHaveCSS('color', 'rgb(0, 0, 0)');
|
||||||
await page.screenshot({ path: testInfo.outputPath('yearbook-nation-contrast-mobile.png'), fullPage: true });
|
await page.screenshot({ path: testInfo.outputPath('yearbook-nation-contrast-mobile.png'), fullPage: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('왕조의 역사 보기는 해당 기수의 지도 레이아웃으로 연감을 렌더한다', async ({ page }, testInfo) => {
|
||||||
|
await installArchiveViews(page);
|
||||||
|
await page.setViewportSize({ width: 1200, height: 800 });
|
||||||
|
await page.goto('dynasty');
|
||||||
|
const archivedHistory = page.getByRole('link', { name: '역사 보기' }).last();
|
||||||
|
await expect(archivedHistory).toHaveAttribute('href', /yearbook\?serverID=che-current-1$/);
|
||||||
|
await archivedHistory.click();
|
||||||
|
|
||||||
|
await expect(page.locator('.history-grid')).toBeVisible();
|
||||||
|
await expect(page.locator('.map-area')).toHaveClass(/map-theme-miniche/);
|
||||||
|
await expect(page.locator('.map-bgroad')).toHaveCSS('background-image', /miniche_road\.png/);
|
||||||
|
await expect(page.locator('.nation-position tbody tr')).toHaveCount(2);
|
||||||
|
const mapEvidence = await page.locator('.map-area').evaluate((area) => {
|
||||||
|
const road = area.querySelector<HTMLElement>('.map-bgroad');
|
||||||
|
const background = area.querySelector<HTMLImageElement>('.map-background-image');
|
||||||
|
return {
|
||||||
|
mapClass: area.className,
|
||||||
|
mapRect: area.getBoundingClientRect().toJSON(),
|
||||||
|
backgroundRect: background?.getBoundingClientRect().toJSON(),
|
||||||
|
backgroundNaturalSize: background
|
||||||
|
? { width: background.naturalWidth, height: background.naturalHeight }
|
||||||
|
: null,
|
||||||
|
backgroundObjectFit: background ? getComputedStyle(background).objectFit : null,
|
||||||
|
roadBackground: road ? getComputedStyle(road).backgroundImage : null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
expect(mapEvidence.mapRect.width / mapEvidence.mapRect.height).toBeCloseTo(7 / 5, 2);
|
||||||
|
expect(mapEvidence.backgroundNaturalSize?.width).toBeGreaterThan(0);
|
||||||
|
await page.screenshot({ path: testInfo.outputPath('dynasty-history-map-desktop.png'), fullPage: true });
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 500, height: 800 });
|
||||||
|
await expect(page.locator('.map-area')).toHaveClass(/map-theme-miniche/);
|
||||||
|
const mobileEvidence = await page.locator('.map-area').evaluate((area) => ({
|
||||||
|
mapClass: area.className,
|
||||||
|
mapRect: area.getBoundingClientRect().toJSON(),
|
||||||
|
roadBackground: getComputedStyle(area.querySelector<HTMLElement>('.map-bgroad')!).backgroundImage,
|
||||||
|
}));
|
||||||
|
expect(mobileEvidence.mapRect.width / mobileEvidence.mapRect.height).toBeCloseTo(7 / 5, 2);
|
||||||
|
await writeFile(
|
||||||
|
testInfo.outputPath('dynasty-history-map-geometry.json'),
|
||||||
|
JSON.stringify({ desktop: mapEvidence, mobile: mobileEvidence }, null, 2)
|
||||||
|
);
|
||||||
|
await page.screenshot({ path: testInfo.outputPath('dynasty-history-map-mobile.png'), fullPage: true });
|
||||||
|
});
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { trpc } from '../utils/trpc';
|
|||||||
const { pageExitLabel, exitPage } = usePageExit();
|
const { pageExitLabel, exitPage } = usePageExit();
|
||||||
|
|
||||||
type YearbookRange = Awaited<ReturnType<typeof trpc.yearbook.getRange.query>>;
|
type YearbookRange = Awaited<ReturnType<typeof trpc.yearbook.getRange.query>>;
|
||||||
type MapLayout = Awaited<ReturnType<typeof trpc.public.getMapLayout.query>>;
|
type MapLayout = YearbookRange['mapLayout'];
|
||||||
type HistoryData = {
|
type HistoryData = {
|
||||||
year: number;
|
year: number;
|
||||||
month: number;
|
month: number;
|
||||||
@@ -114,12 +114,11 @@ watch(selectedYearMonth, () => {
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const [loadedRange, loadedLayout] = await Promise.all([
|
const loadedRange = await trpc.yearbook.getRange.query(
|
||||||
trpc.yearbook.getRange.query(serverID.value ? { serverID: serverID.value } : undefined),
|
serverID.value ? { serverID: serverID.value } : undefined
|
||||||
trpc.public.getMapLayout.query(),
|
);
|
||||||
]);
|
|
||||||
range.value = loadedRange;
|
range.value = loadedRange;
|
||||||
mapLayout.value = loadedLayout;
|
mapLayout.value = loadedRange.mapLayout;
|
||||||
selectedYearMonth.value = loadedRange.currentYearMonth;
|
selectedYearMonth.value = loadedRange.currentYearMonth;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorMessage.value = error instanceof Error ? error.message : '연감 범위를 불러오지 못했습니다.';
|
errorMessage.value = error instanceof Error ? error.message : '연감 범위를 불러오지 못했습니다.';
|
||||||
|
|||||||
Reference in New Issue
Block a user