feat: apply feedback updates and streamline online delivery
Publish GitHub Pages output / publish (push) Successful in 34s
Publish GitHub Pages output / publish (push) Successful in 34s
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
import cardSummary from '$lib/data/cards-summary.json';
|
||||
import boardSummary from '$lib/data/boards-summary.json';
|
||||
import memorySummary from '$lib/data/memory-summary.json';
|
||||
import { calculateBoardEffects, calculateUnitPower, maximumWeightMatching, plainSkillText } from '$lib/engine/index.ts';
|
||||
import { calculateBoardEffects, calculateUnitPower, cardMatchesSearch, maximumWeightMatching, plainSkillText } from '$lib/engine/index.ts';
|
||||
import { memoryParameterPermil } from '$lib/engine/memory.ts';
|
||||
import type { BoardCardPlacements, CardListPreferences, CollectionCardState } from '$lib/models/user-data';
|
||||
import { createUserData } from '$lib/models/user-data';
|
||||
@@ -39,6 +39,7 @@
|
||||
coreCubeFreeOnly: boolean;
|
||||
};
|
||||
type GreenNodeCost = { points: number; greenCubes: number; greenCoreCubes: number };
|
||||
type BoardMaterialTotal = { resourceId: string; label: string; quantity: number };
|
||||
type GreenNodeEfficiency = {
|
||||
memberId: string;
|
||||
memberName: string;
|
||||
@@ -68,6 +69,16 @@
|
||||
const EFFICIENCY_FORMATION_STORAGE_KEY = 'holodori.green-efficiency-formation.v1';
|
||||
const GREEN_CUBE_ID = 'item-skill_tree_allmember-1';
|
||||
const GREEN_CORE_CUBE_ID = 'item-skill_tree_allmember-2';
|
||||
const boardMaterialLabels: Readonly<Record<string, string>> = {
|
||||
'item-skill_tree_card-1': '블루 큐브',
|
||||
'item-skill_tree_card-2': '블루 코어 큐브',
|
||||
'item-skill_tree_allmember-1': '그린 큐브',
|
||||
'item-skill_tree_allmember-2': '그린 코어 큐브',
|
||||
'item-skill_tree_leader-1': '레드 큐브',
|
||||
'item-skill_tree_leader-2': '레드 코어 큐브',
|
||||
'item-skill_tree_content-1': '옐로 큐브',
|
||||
'item-skill_tree_content-2': '옐로 코어 큐브'
|
||||
};
|
||||
const allCards = cardSummary.cards;
|
||||
const boardMembers = boardSummary.members as BoardMember[];
|
||||
const memberOrderByName = new Map(boardMembers.map((member) => [member.name, member.order]));
|
||||
@@ -131,7 +142,7 @@
|
||||
$: collectionCount = Object.values(userData.collection).filter((state) => state.owned).length;
|
||||
$: boardCount = Object.values(userData.memberBoards).filter((nodes) => nodes.length > 0).length;
|
||||
$: memoryPermil = memoryParameterPermil(userData.memoryCount, memorySummary.effects);
|
||||
$: filteredCards = managedCards.filter((card) => `${card.name} ${card.versionName}`.toLowerCase().includes(search.trim().toLowerCase()));
|
||||
$: filteredCards = managedCards.filter((card) => cardMatchesSearch(card, search));
|
||||
$: filteredFiveStarCards = filteredCards.filter((card) => card.rarity === 5);
|
||||
$: filteredFourStarCards = filteredCards.filter((card) => card.rarity === 4);
|
||||
$: selectedBoardMember = boardMembers.find((member) => member.id === selectedMemberId) ?? null;
|
||||
@@ -148,9 +159,10 @@
|
||||
selectedBoardMember?.positionGroupId ?? ''
|
||||
);
|
||||
$: filteredConnectCards = connectCards.filter((card) => {
|
||||
const query = boardCardSearch.trim().toLowerCase();
|
||||
return !query || `${card.name} ${card.versionName}`.toLowerCase().includes(query);
|
||||
return cardMatchesSearch(card, boardCardSearch);
|
||||
});
|
||||
$: selectedBoardMaterialTotals = boardMaterialTotals(selectedMemberId, selectedActiveBoardNodes);
|
||||
$: selectedBoardPointTotal = selectedActiveBoardNodes.reduce((total, groupId) => total + (nodeForBoardMember(selectedMemberId, groupId)?.skillTreePointCost ?? 0), 0);
|
||||
$: efficiencyLeaderOutfitOptions = efficiencyFormation.leaderCharacterId
|
||||
? fiveStarCards.filter((card) => card.characterId === efficiencyFormation.leaderCharacterId && card.details.costume)
|
||||
.filter((card, index, cards) => cards.findIndex((candidate) => candidate.details.costume?.name === card.details.costume?.name) === index)
|
||||
@@ -574,6 +586,20 @@
|
||||
}, { points: 0, greenCubes: 0, greenCoreCubes: 0 });
|
||||
}
|
||||
|
||||
function boardMaterialTotals(memberId: string, groupIds: readonly string[]): BoardMaterialTotal[] {
|
||||
const totals = new Map<string, number>();
|
||||
for (const groupId of groupIds) {
|
||||
const node = nodeForBoardMember(memberId, groupId);
|
||||
for (const consumption of node?.consumptions ?? []) {
|
||||
const quantity = Number(consumption.quantity) || 0;
|
||||
totals.set(consumption.resourceId, (totals.get(consumption.resourceId) ?? 0) + quantity);
|
||||
}
|
||||
}
|
||||
return [...totals.entries()]
|
||||
.filter(([, quantity]) => quantity > 0)
|
||||
.map(([resourceId, quantity]) => ({ resourceId, quantity, label: boardMaterialLabels[resourceId] ?? resourceId }));
|
||||
}
|
||||
|
||||
function currentFormationPower(memberBoards: Record<string, string[]>, boardCards: BoardCardPlacements = userData.boardCards): number {
|
||||
if (!efficiencyFormationReady) return 0;
|
||||
const members = efficiencyFormation.cardIds.map((id, index) => ({
|
||||
@@ -901,6 +927,21 @@
|
||||
|
||||
function assignBoardCard(cardId: string) {
|
||||
if (!selectedSocketGroupId) return;
|
||||
const destinationCardId = selectedBoardCardPlacements[selectedSocketGroupId];
|
||||
const sourcePlacement = Object.entries(userData.boardCards).flatMap(([memberId, placements]) =>
|
||||
Object.entries(placements).map(([socketId, placedCardId]) => ({ memberId, socketId, placedCardId }))
|
||||
).find((placement) => placement.placedCardId === cardId
|
||||
&& !(placement.memberId === selectedMemberId && placement.socketId === selectedSocketGroupId));
|
||||
const warnings = [];
|
||||
if (destinationCardId && destinationCardId !== cardId) {
|
||||
const destinationCard = connectCardById.get(destinationCardId);
|
||||
warnings.push(`현재 ${destinationCard ? `${destinationCard.rarity}★ ${cardLabel(destinationCard)}` : destinationCardId} 카드를 덮어씁니다.`);
|
||||
}
|
||||
if (sourcePlacement) {
|
||||
const sourceMember = boardMembers.find((member) => member.id === sourcePlacement.memberId)?.name ?? sourcePlacement.memberId;
|
||||
warnings.push(`${sourceMember} 보드에 배치된 같은 카드를 이곳으로 이동합니다.`);
|
||||
}
|
||||
if (warnings.length > 0 && !window.confirm(`${warnings.join('\n')}\n계속할까요?`)) return;
|
||||
automaticPlacementSnapshot = null;
|
||||
automaticPlacementResult = [];
|
||||
const boardCards = Object.fromEntries(Object.entries(userData.boardCards).map(([memberId, placements]) => [memberId, { ...placements }]));
|
||||
@@ -1278,7 +1319,7 @@
|
||||
</section>
|
||||
<section class="efficiency-column removal" aria-labelledby="removal-efficiency-title">
|
||||
<div class="efficiency-column-heading">
|
||||
<div><h3 id="removal-efficiency-title">초기화 검토</h3><p>환급 큐브 대비 종합력 손실이 낮은 순서</p></div>
|
||||
<div><h3 id="removal-efficiency-title">초기화 검토</h3><p>환급 큐브 대비 종합력 손실이 낮은 순서 · 기수 보너스 상한 초과분이나 현재 편성에 적용되지 않는 노드는 손실이 0일 수 있습니다.</p></div>
|
||||
</div>
|
||||
{#if greenEfficiencyResult.removals.length > 0}
|
||||
<div class="efficiency-list">
|
||||
@@ -1287,7 +1328,7 @@
|
||||
<button type="button" on:click={() => focusEfficiencyNode(entry)}>
|
||||
<span class="efficiency-rank">{index + 1}</span>
|
||||
<span class="efficiency-node"><small>{entry.memberName}</small><strong>{entry.description}</strong></span>
|
||||
<span class="efficiency-power"><strong>-{entry.powerDelta.toLocaleString('ko-KR')}</strong></span>
|
||||
<span class="efficiency-power"><strong>{entry.powerDelta === 0 ? '0 · 영향 없음' : `-${entry.powerDelta.toLocaleString('ko-KR')}`}</strong></span>
|
||||
<span class="efficiency-node-count"><strong>{entry.changedNodeCount}</strong></span>
|
||||
<span class="efficiency-cost"><strong>{entry.cost.greenCubes.toLocaleString('ko-KR')} / {entry.cost.greenCoreCubes.toLocaleString('ko-KR')}</strong></span>
|
||||
<span class="efficiency-rate"><strong>{formatEfficiency(entry.greenCubeEfficiency)}</strong></span>
|
||||
@@ -1308,6 +1349,10 @@
|
||||
</select>
|
||||
<small><kbd>A</kbd> 이전 멤버 · <kbd>D</kbd> 다음 멤버</small>
|
||||
</label>
|
||||
<div class="member-navigation" aria-label="멤버 보드 이동">
|
||||
<button type="button" on:click={() => selectAdjacentBoardMember(-1)} aria-label="이전 멤버 보드">‹ 이전</button>
|
||||
<button type="button" on:click={() => selectAdjacentBoardMember(1)} aria-label="다음 멤버 보드">다음 ›</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if selectedBoardMember}
|
||||
@@ -1315,6 +1360,13 @@
|
||||
<p><strong>{selectedActiveBoardNodeCount}</strong> / {selectedBoardNodeCount}개 활성화</p>
|
||||
<div><button type="button" on:click={activateAllBlueNodes}>모든 파란 노드 활성화</button><button type="button" on:click={() => setAllBoardNodes(true)}>모두 활성화</button><button type="button" on:click={() => setAllBoardNodes(false)}>모두 해제</button></div>
|
||||
</div>
|
||||
<div class="board-material-summary" aria-label="활성 보드 재료 총합">
|
||||
<span><small>노드 포인트</small><strong>{selectedBoardPointTotal.toLocaleString('ko-KR')}</strong></span>
|
||||
{#each selectedBoardMaterialTotals as material (material.resourceId)}
|
||||
<span><small>{material.label}</small><strong>{material.quantity.toLocaleString('ko-KR')}</strong></span>
|
||||
{/each}
|
||||
{#if selectedBoardMaterialTotals.length === 0}<p>활성 노드에 사용된 재료가 없습니다.</p>{/if}
|
||||
</div>
|
||||
<div
|
||||
class="board-scroll"
|
||||
class:dragging={boardDragging}
|
||||
@@ -1533,11 +1585,19 @@
|
||||
.member-select select { height: 38px; padding: 0 10px; border: 1px solid #394252; border-radius: 8px; color: #dfe3ea; background: #12171f; }
|
||||
.member-select small { color: #6f7888; font-size: 11px; }
|
||||
.member-select kbd { display: inline-grid; min-width: 18px; height: 18px; padding: 0 4px; place-items: center; border: 1px solid #465064; border-bottom-width: 2px; border-radius: 4px; color: #c9d0dc; background: #171d27; font: 700 10px/1 Pretendard, "Noto Sans KR", sans-serif; }
|
||||
.member-navigation { display: flex; gap: 7px; }
|
||||
.member-navigation button { min-width: 82px; height: 38px; padding: 0 12px; border: 1px solid #465064; border-radius: 8px; color: #d5dbe4; background: #202733; font-size: 13px; font-weight: 850; cursor: pointer; }
|
||||
.member-navigation button:hover { border-color: #d96b9a; color: #f4b0cc; }
|
||||
.board-toolbar { display: flex; align-items: center; justify-content: space-between; margin: 20px 0 10px; }
|
||||
.board-toolbar p { margin: 0; color: #929cad; font-size: 14px; }
|
||||
.board-toolbar p strong { color: #f0a1c2; font-size: 16px; }
|
||||
.board-toolbar div { display: flex; gap: 6px; }
|
||||
.board-toolbar button { padding: 6px 9px; border: 1px solid #394252; border-radius: 7px; color: #aeb7c5; background: #202630; font-size: 12px; font-weight: 750; cursor: pointer; }
|
||||
.board-material-summary { display: flex; flex-wrap: wrap; gap: 7px; margin: 0 0 12px; padding: 10px; border: 1px solid #303a47; border-radius: 10px; background: #131922; }
|
||||
.board-material-summary span { display: grid; gap: 2px; min-width: 108px; padding: 7px 9px; border-radius: 7px; background: #1c2430; }
|
||||
.board-material-summary small { color: #8490a0; font-size: 11px; }
|
||||
.board-material-summary strong { color: #dce3eb; font-size: 14px; }
|
||||
.board-material-summary p { margin: 0; color: #8792a2; font-size: 13px; }
|
||||
.automatic-placement { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 18px; margin: 0 0 18px; padding: 12px 14px; border: 1px solid #355044; border-radius: 10px; background: rgb(58 113 83 / 10%); }
|
||||
.automatic-placement > div:first-child { display: grid; gap: 4px; min-width: 0; }
|
||||
.automatic-placement strong { color: #b7dec7; font-size: 14px; }
|
||||
@@ -1717,6 +1777,8 @@
|
||||
.board-editor { padding: 21px 18px; }
|
||||
.board-heading { align-items: stretch; flex-direction: column; }
|
||||
.member-select { width: 100%; }
|
||||
.member-navigation { display: grid; grid-template-columns: 1fr 1fr; }
|
||||
.member-navigation button { width: 100%; min-height: 44px; }
|
||||
.board-toolbar { align-items: flex-start; gap: 10px; }
|
||||
.automatic-placement { align-items: stretch; flex-direction: column; gap: 10px; }
|
||||
.automatic-placement-actions { justify-content: flex-end; }
|
||||
|
||||
Reference in New Issue
Block a user