merge: 최신 main을 tRPC JSON 본문 전송에 통합

This commit is contained in:
2026-08-17 11:12:09 +00:00
49 changed files with 1182 additions and 162 deletions
+1
View File
@@ -1,3 +1,4 @@
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css');
@import 'tailwindcss';
@import './styles/tokens.css';
@import './styles/legacy-controls.css';
@@ -685,7 +685,9 @@ const clickOutsideMenu = (event: Event) => {
class="command-picker"
:class="{ 'recruitment-picker': isRecruitmentCommand }"
data-testid="command-picker"
:style="isRecruitmentCommand || props.compact ? undefined : { top: quickPickerTop }"
:style="
isRecruitmentCommand || quickTarget === null || props.compact ? undefined : { top: quickPickerTop }
"
:role="isRecruitmentCommand ? 'dialog' : undefined"
:aria-modal="isRecruitmentCommand ? 'true' : undefined"
:aria-label="
@@ -1146,12 +1148,15 @@ const clickOutsideMenu = (event: Event) => {
}
.mobile.compact .editor-layout {
height: 360px;
min-height: 370px;
display: grid;
grid-template-columns: 109px 391px;
grid-template-rows: auto auto;
}
.mobile.compact .control-pad {
order: initial;
grid-column: 1;
grid-row: 1 / -1;
min-height: 0;
padding: 0;
grid-template-columns: 1fr;
@@ -1159,6 +1164,8 @@ const clickOutsideMenu = (event: Event) => {
}
.mobile.compact .queue-area {
order: initial;
grid-column: 2;
grid-row: 1;
padding-top: 10px;
}
.mobile.compact .queue-grid {
@@ -1189,8 +1196,8 @@ const clickOutsideMenu = (event: Event) => {
overflow: visible;
}
.mobile.compact .advanced-actions {
right: 0;
bottom: 0;
left: 109px;
position: static;
grid-column: 2;
grid-row: 2;
}
</style>
@@ -75,7 +75,10 @@ export const formatReservedCommandBrief = (
const commandName = commandNames.get(action) ?? defaultCommandName(action);
const cityName = optionLabel(input?.cities ?? [], firstValue(args, ['destCityId', 'destCityID']));
const nationName = optionLabel(input?.nations ?? [], firstValue(args, ['destNationId', 'destNationID']));
const generalName = optionLabel(input?.generals ?? [], firstValue(args, ['destGeneralId', 'destGeneralID']));
const generalName = optionLabel(
input?.generalTargets?.[action] ?? input?.generals ?? [],
firstValue(args, ['destGeneralId', 'destGeneralID'])
);
if (CITY_TO_COMMANDS.has(action) && cityName) {
return `${wrappedWithParticle(cityName, '으로')} ${commandName}`;
@@ -95,6 +95,7 @@ export type CommandTable = {
cities: CommandOption[];
nations: CommandOption[];
generals: CommandOption[];
generalTargets?: Record<string, CommandOption[]>;
crewTypes: CommandOption[];
armTypes: CommandOption[];
nationTypes: CommandOption[];
@@ -33,6 +33,9 @@ const visibleFields = computed(() => props.fields.filter((entry) => entry.kind !
const optionsFor = (field: CommandInputField): CommandOption[] => {
if (field.options) return field.options;
if (!field.optionSource) return [];
if (field.optionSource === 'generals') {
return props.options.generalTargets?.[props.commandKey] ?? props.options.generals;
}
if (field.optionSource === 'items') {
return props.options.items[String(values.itemType ?? '')] ?? [];
}
@@ -41,7 +41,7 @@ const formattedLogs = computed(() =>
.recent-log-list {
min-width: 0;
color: #fff;
font-family: 'Times New Roman', serif;
font-family: var(--sammo-font-sans);
font-size: 14px;
line-height: 1.35;
}
@@ -28,8 +28,10 @@ const roundColumns = computed(() => [
]);
const desktopX = [110, 355, 600, 845, 1090];
const cardWidth = 190;
const desktopSlotHeight = 88;
const desktopCanvasHeight = desktopSlotHeight * 16;
const slotY = (columnIndex: number, slotIndex: number) => {
const slotHeight = 72 * 2 ** columnIndex;
const slotHeight = desktopSlotHeight * 2 ** columnIndex;
return slotHeight / 2 + slotIndex * slotHeight;
};
const connections = computed(() =>
@@ -77,8 +79,8 @@ const mobilePairs = computed(() => {
<div class="desktop-round-labels" aria-hidden="true">
<strong v-for="label in roundLabels" :key="label">{{ label }}</strong>
</div>
<div class="desktop-bracket-canvas">
<svg viewBox="0 0 1200 1152" aria-hidden="true">
<div class="desktop-bracket-canvas" :style="{ height: `${desktopCanvasHeight}px` }">
<svg :viewBox="`0 0 1200 ${desktopCanvasHeight}`" aria-hidden="true">
<g v-for="connection in connections" :key="connection.id">
<path
class="bracket-connector"
@@ -177,7 +179,6 @@ const mobilePairs = computed(() => {
.desktop-bracket-canvas {
position: relative;
width: 100%;
height: 1152px;
}
.desktop-bracket-canvas svg {
position: absolute;
@@ -200,7 +201,7 @@ const mobilePairs = computed(() => {
display: grid;
box-sizing: border-box;
width: clamp(140px, 16vw, 190px);
min-height: 68px;
min-height: 82px;
align-items: center;
overflow: hidden;
transform: translate(-50%, -50%);
@@ -265,7 +266,7 @@ const mobilePairs = computed(() => {
.mobile-bracket-name {
box-sizing: border-box;
min-width: 0;
min-height: 68px;
min-height: 82px;
overflow: hidden;
border: 1px solid #555;
background: rgb(58 33 24 / 94%);
+11 -5
View File
@@ -304,16 +304,16 @@ const placeBet = async (targetId: number) => {
background: #142b42 var(--sammo-texture-blue);
}
.title {
height: 55.6875px;
min-height: 68px;
padding: 0;
font-size: 14px;
line-height: 19.1875px;
}
.close-button {
display: block;
width: 62px;
height: 35.5px;
padding: 8px 12px;
width: 88px;
height: 44px;
padding: 10px 16px;
border: 1px solid #375a7f;
border-radius: 5.25px;
background: #375a7f;
@@ -323,10 +323,16 @@ const placeBet = async (targetId: number) => {
text-decoration: none;
}
.toolbar {
min-height: 36.5px;
min-height: 46px;
padding: 1px;
text-align: left;
}
.toolbar button {
min-width: 72px;
height: 44px;
padding: 10px 16px;
font-size: 14px;
}
.error {
min-height: 32px;
padding: 5px;
+1 -3
View File
@@ -252,9 +252,7 @@ onMounted(() => {
padding: 8px;
color: #000;
background: #fff;
font:
16px/normal 'Times New Roman',
serif;
font: 16px/normal var(--sammo-font-sans);
}
.legacy-board-page {
+63 -25
View File
@@ -352,22 +352,34 @@ const repeatTurns = async (amount: number) => {
</div>
<div class="chief-overview-frame">
<div class="chief-overview">
<template v-for="chief in chiefViews" :key="chief.officerLevel">
<ChiefTurnCard
v-if="chief.name"
:officer-level-text="chief.officerLevelText"
:name="chief.name"
:npc-state="chief.npcState"
:rows="chief.rows"
:compact="true"
:selected="chief.officerLevel === selectedChief?.officerLevel"
:is-me="chief.officerLevel === data.me.officerLevel"
:clickable="true"
:turn-time-label="chief.rows[0]?.time"
@select="selectedChiefLevel = chief.officerLevel"
/>
<div v-else class="empty-chief-slot" aria-hidden="true"></div>
</template>
<div
v-for="(rowChiefs, rowIndex) in [chiefViews.slice(0, 4), chiefViews.slice(4, 8)]"
:key="rowIndex"
class="chief-overview-row"
>
<div class="overview-turn-index legacy-bg0" :aria-label="`${rowIndex + 1} 번호`">
<span></span><span v-for="idx in data.maxTurns" :key="idx">{{ idx }}</span>
</div>
<template v-for="chief in rowChiefs" :key="chief.officerLevel">
<ChiefTurnCard
v-if="chief.name"
:officer-level-text="chief.officerLevelText"
:name="chief.name"
:npc-state="chief.npcState"
:rows="chief.rows"
:compact="true"
:selected="chief.officerLevel === selectedChief?.officerLevel"
:is-me="chief.officerLevel === data.me.officerLevel"
:clickable="true"
:turn-time-label="chief.rows[0]?.time"
@select="selectedChiefLevel = chief.officerLevel"
/>
<div v-else class="empty-chief-slot" aria-hidden="true"></div>
</template>
<div class="overview-turn-index legacy-bg0" :aria-label="`${rowIndex + 1} 번호`">
<span></span><span v-for="idx in data.maxTurns" :key="idx">{{ idx }}</span>
</div>
</div>
</div>
</div>
</section>
@@ -750,20 +762,25 @@ const repeatTurns = async (amount: number) => {
.chief-overview-frame {
width: 500px;
height: 310px;
margin-top: -3px;
margin-top: 0;
margin-bottom: 11px;
overflow: hidden;
}
.chief-overview {
width: 445px;
width: 500px;
height: 310px;
margin-top: 0;
display: flex;
flex-direction: column;
}
.chief-overview-row {
width: 500px;
height: 155px;
display: grid;
grid-template-columns: repeat(4, 111.25px);
grid-auto-rows: 155px;
grid-template-columns: 12px repeat(4, 119px) 12px;
}
.chief-overview :deep(.chief-card) {
width: 111.25px;
width: 119px;
height: 155px;
border: 0;
border-left: 1px solid #fff;
@@ -787,13 +804,34 @@ const repeatTurns = async (amount: number) => {
box-sizing: border-box;
height: 20px !important;
min-height: 20px !important;
grid-template-rows: none;
grid-template-columns: 1fr;
grid-template-rows: 10px 10px;
line-height: 10px;
}
.chief-overview :deep(.compact-name),
.chief-overview :deep(.compact-meta) {
height: 10px;
line-height: 10px;
}
.chief-overview :deep(.row-time),
.chief-overview :deep(.row-action) {
display: grid;
place-items: center;
}
.overview-turn-index {
display: grid;
grid-template-rows: 20px repeat(12, 11.25px);
width: 12px;
height: 155px;
color: #fff;
font-size: 0.55rem;
line-height: 11.25px;
text-align: center;
}
.overview-turn-index span {
display: grid;
place-items: center;
}
.mobile-readonly {
width: 404px;
height: 420px;
@@ -879,7 +917,7 @@ const repeatTurns = async (amount: number) => {
@media (max-width: 1024px) {
.chief-overview {
grid-template-columns: repeat(4, 111.25px);
width: 500px;
}
}
@@ -891,8 +929,8 @@ const repeatTurns = async (amount: number) => {
.chief-grid-row > .empty-chief-slot {
height: 384px;
}
.chief-overview > .empty-chief-slot {
width: 111.25px;
.chief-overview-row > .empty-chief-slot {
width: 119px;
height: 155px;
}
</style>
@@ -366,7 +366,7 @@ const generalImage = (general: General): string => resolveGeneralIconUrl(general
border: 1px solid #767676;
background: #6b6b6b;
color: #fff;
font-family: Arial, sans-serif;
font-family: var(--sammo-font-sans);
font-size: 13.3333px;
}
.selector {
@@ -470,7 +470,7 @@ const generalImage = (general: General): string => resolveGeneralIconUrl(general
background: #6c757d;
color: #fff;
padding: 0.25rem 0.5rem;
font-family: Arial, sans-serif;
font-family: var(--sammo-font-sans);
font-size: 14px;
line-height: 1;
text-decoration: none;
@@ -305,7 +305,7 @@ onMounted(loadDetail);
width: 1000px;
margin: 8px auto 0;
color: #fff;
font-family: 'Times New Roman', serif;
font-family: var(--sammo-font-sans);
font-size: 16px;
line-height: normal;
}
@@ -384,7 +384,7 @@ onMounted(loadDetail);
padding: 1px 6px;
background: buttonface;
color: buttontext;
font-family: Arial;
font-family: var(--sammo-font-sans);
font-size: 13.3333px;
font-weight: 400;
line-height: normal;
@@ -231,7 +231,7 @@ onMounted(loadDynasty);
padding: 1px 6px;
background: buttonface;
color: buttontext;
font-family: Arial;
font-family: var(--sammo-font-sans);
font-size: 13.3333px;
font-weight: 400;
line-height: normal;
+1 -6
View File
@@ -778,12 +778,7 @@ onMounted(() => {
padding: 0 7px;
color: #fff;
height: 1597px;
font:
14px/21px Pretendard,
'Apple SD Gothic Neo',
'Noto Sans KR',
'Malgun Gothic',
sans-serif;
font: 14px/21px var(--sammo-font-sans);
}
.inherit-page.legacy-bg0 {
+3 -2
View File
@@ -66,11 +66,12 @@ const nationAccess = computed(() => ({
}));
const nationColor = computed(() => nation.value?.color ?? '#000000');
const voteActive = computed(() => Boolean(frontStatus.value?.latestVote));
const recordTimeSuffixPattern = /\d{2}:\d{2}(?:<\/>)?\s*$/u;
const formatRecord = (entry: { text: string; createdAt?: string | Date }, appendTime = false): string => {
if (!appendTime || /\d{2}:\d{2}\s*$/u.test(entry.text)) return formatLog(entry.text);
if (!appendTime || recordTimeSuffixPattern.test(entry.text)) return formatLog(entry.text);
const time = formatServerDateTime(entry.createdAt, { format: 'hourMinute', fallback: '' });
if (!time) return formatLog(entry.text);
return formatLog(`${entry.text} ${time}`);
return formatLog(`${entry.text} <1>${time}</>`);
};
let surveyNoticeTimer: ReturnType<typeof setTimeout> | null = null;
@@ -451,7 +451,7 @@ onMounted(() => {
width: 500px;
margin: 0 auto;
color: #fff;
font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic';
font-family: var(--sammo-font-sans);
font-size: 14px;
line-height: 1.5;
}
@@ -522,12 +522,7 @@ onMounted(() => void loadPersonnel());
min-height: 100vh;
margin: 0 auto;
color: #fff;
font:
14px/1.3 Pretendard,
'Apple SD Gothic Neo',
'Noto Sans KR',
'Malgun Gothic',
sans-serif;
font: 14px/1.3 var(--sammo-font-sans);
}
.legacy-table {
width: 1000px;
@@ -174,12 +174,7 @@ onMounted(load);
.secret-page {
width: auto;
margin: 0;
font:
14px Pretendard,
'Apple SD Gothic Neo',
'Noto Sans KR',
'Malgun Gothic',
sans-serif;
font: 14px var(--sammo-font-sans);
color: #fff;
}
.layout {
@@ -437,12 +437,7 @@ onMounted(() => void loadStratFinan());
margin: 0 auto;
color: #fff;
background: var(--sammo-texture-walnut);
font:
14px/1.3 Pretendard,
'Apple SD Gothic Neo',
'Noto Sans KR',
'Malgun Gothic',
sans-serif;
font: 14px/1.3 var(--sammo-font-sans);
}
.tiptap-compat-controls {
display: none;
+1 -1
View File
@@ -165,7 +165,7 @@ onMounted(() => {
min-height: 100vh;
margin: 0 auto;
color: #fff;
font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic';
font-family: var(--sammo-font-sans);
font-size: 14px;
line-height: 1.3;
}
@@ -515,8 +515,6 @@ onBeforeUnmount(() => {
</template>
<style scoped>
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css');
.select-pool-page {
width: 1000px;
min-width: 1000px;
+1 -1
View File
@@ -423,7 +423,7 @@ onMounted(() => {
.pageVote {
margin: 0 auto;
color: #fff;
font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic';
font-family: var(--sammo-font-sans);
font-size: 14px;
line-height: 1.5;
}
+11 -5
View File
@@ -385,16 +385,16 @@ const start = async () => {
background: #142b42 var(--sammo-texture-blue);
}
.legacy-title {
height: 55.6875px;
min-height: 68px;
padding: 0;
font-size: 14px;
line-height: 19.1875px;
}
.close-button {
display: block;
width: 62px;
height: 35.5px;
padding: 8px 12px;
width: 88px;
height: 44px;
padding: 10px 16px;
border: 1px solid #375a7f;
border-radius: 5.25px;
background: #375a7f;
@@ -404,9 +404,15 @@ const start = async () => {
text-decoration: none;
}
.toolbar {
min-height: 36.5px;
min-height: 46px;
padding: 1px;
}
.toolbar button {
min-width: 72px;
height: 44px;
padding: 10px 16px;
font-size: 14px;
}
.operator-row,
.state-row,
.error-row,
+1 -1
View File
@@ -361,7 +361,7 @@ onMounted(() => {
margin: 0 auto;
color: #fff;
background: transparent;
font-family: Pretendard, 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic';
font-family: var(--sammo-font-sans);
font-size: 14px;
line-height: 1.5;
}
+3 -3
View File
@@ -378,10 +378,10 @@ onMounted(async () => {
grid-column: 1 / -1;
}
.history-log:first-of-type {
height: 128px;
min-height: 128px;
}
.history-log:last-of-type {
height: 65px;
min-height: 65px;
}
.dropdown-compat-buttons {
display: none;
@@ -441,7 +441,7 @@ onMounted(async () => {
order: 4;
}
.history-log:first-of-type {
height: 149px;
min-height: 149px;
margin-bottom: 0;
}
}