Add locations to todo items and automate Gitea deploy
Some checks failed
Redeploy Docker Compose / redeploy (push) Failing after 2s
Some checks failed
Redeploy Docker Compose / redeploy (push) Failing after 2s
This commit is contained in:
@@ -25,6 +25,18 @@ function isUpgradeResult(result: SearchResult): result is UpgradeSearchResult {
|
||||
return 'isUpgrade' in result;
|
||||
}
|
||||
|
||||
function normalizeKnownLocations(value: unknown): string[] | undefined {
|
||||
if (!Array.isArray(value)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const normalized = value
|
||||
.map((location) => (typeof location === 'string' ? location.trim() : ''))
|
||||
.filter((location) => Boolean(location));
|
||||
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
}
|
||||
|
||||
function ItemIcon({
|
||||
src,
|
||||
alt,
|
||||
@@ -85,6 +97,9 @@ function loadTodoItems(): TodoItem[] {
|
||||
typeof row.name === 'string' &&
|
||||
typeof row.iconUrl === 'string' &&
|
||||
(row.rarity === undefined || typeof row.rarity === 'string') &&
|
||||
(row.knownLocations === undefined ||
|
||||
(Array.isArray(row.knownLocations) &&
|
||||
row.knownLocations.every((location: unknown) => typeof location === 'string'))) &&
|
||||
typeof row.quantity === 'number' &&
|
||||
typeof row.completed === 'boolean'
|
||||
);
|
||||
@@ -92,6 +107,7 @@ function loadTodoItems(): TodoItem[] {
|
||||
.map((item) => ({
|
||||
...item,
|
||||
rarity: typeof item.rarity === 'string' ? item.rarity : undefined,
|
||||
knownLocations: normalizeKnownLocations(item.knownLocations),
|
||||
quantity: Math.max(1, Math.floor(item.quantity)),
|
||||
}));
|
||||
} catch {
|
||||
@@ -177,6 +193,7 @@ function decodeListFromUrl(encoded: string, allItems: SearchItem[]): TodoItem[]
|
||||
name: match.name,
|
||||
iconUrl: match.iconUrl,
|
||||
rarity: match.rarity,
|
||||
knownLocations: match.knownLocations,
|
||||
quantity,
|
||||
completed,
|
||||
};
|
||||
@@ -212,11 +229,14 @@ export default function App() {
|
||||
return;
|
||||
}
|
||||
|
||||
const rarityBySlug = new Map(allItems.map((item) => [item.slug, item.rarity]));
|
||||
const itemMetadataBySlug = new Map(
|
||||
allItems.map((item) => [item.slug, { rarity: item.rarity, knownLocations: item.knownLocations }]),
|
||||
);
|
||||
setTodoItems((prevItems) =>
|
||||
prevItems.map((item) => ({
|
||||
...item,
|
||||
rarity: item.rarity ?? rarityBySlug.get(item.slug),
|
||||
rarity: item.rarity ?? itemMetadataBySlug.get(item.slug)?.rarity,
|
||||
knownLocations: item.knownLocations ?? itemMetadataBySlug.get(item.slug)?.knownLocations,
|
||||
})),
|
||||
);
|
||||
|
||||
@@ -359,7 +379,14 @@ export default function App() {
|
||||
const existing = prevItems.find((entry) => entry.slug === item.slug);
|
||||
if (existing) {
|
||||
return prevItems.map((entry) =>
|
||||
entry.slug === item.slug ? { ...entry, quantity: entry.quantity + 1 } : entry,
|
||||
entry.slug === item.slug
|
||||
? {
|
||||
...entry,
|
||||
quantity: entry.quantity + 1,
|
||||
rarity: entry.rarity ?? item.rarity,
|
||||
knownLocations: entry.knownLocations ?? item.knownLocations,
|
||||
}
|
||||
: entry,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -370,6 +397,7 @@ export default function App() {
|
||||
name: item.name,
|
||||
iconUrl: item.iconUrl,
|
||||
rarity: item.rarity,
|
||||
knownLocations: item.knownLocations,
|
||||
quantity: 1,
|
||||
completed: false,
|
||||
},
|
||||
@@ -398,6 +426,8 @@ export default function App() {
|
||||
nextItems[existingIndex] = {
|
||||
...nextItems[existingIndex],
|
||||
quantity: nextItems[existingIndex].quantity + amount,
|
||||
rarity: nextItems[existingIndex].rarity ?? salvageEntry.rarity,
|
||||
knownLocations: nextItems[existingIndex].knownLocations ?? salvageEntry.knownLocations,
|
||||
};
|
||||
continue;
|
||||
}
|
||||
@@ -408,6 +438,7 @@ export default function App() {
|
||||
name: salvageEntry.name,
|
||||
iconUrl: salvageEntry.iconUrl,
|
||||
rarity: salvageEntry.rarity,
|
||||
knownLocations: salvageEntry.knownLocations,
|
||||
quantity: amount,
|
||||
completed: false,
|
||||
});
|
||||
@@ -501,7 +532,9 @@ export default function App() {
|
||||
<header>
|
||||
<div className="header-top">
|
||||
<div>
|
||||
<h1>marathon.todo</h1>
|
||||
<h1>
|
||||
marathon.todo <span className="app-version">v0.0.1</span>
|
||||
</h1>
|
||||
{!minimalMode ? <p>Plan what to loot (or do) in your next Marathon raid.</p> : null}
|
||||
</div>
|
||||
{!minimalMode ? (
|
||||
@@ -738,12 +771,17 @@ export default function App() {
|
||||
<ItemIcon src={item.iconUrl} alt="" size={37} />
|
||||
</span>
|
||||
|
||||
<span
|
||||
className="item-name"
|
||||
style={getRarityGlowColor(item.rarity) ? { color: getRarityGlowColor(item.rarity)! } : undefined}
|
||||
>
|
||||
{item.name}
|
||||
</span>
|
||||
<div className="item-main">
|
||||
<span
|
||||
className="item-name"
|
||||
style={getRarityGlowColor(item.rarity) ? { color: getRarityGlowColor(item.rarity)! } : undefined}
|
||||
>
|
||||
{item.name}
|
||||
</span>
|
||||
{item.knownLocations && item.knownLocations.length > 0 ? (
|
||||
<span className="item-locations">{item.knownLocations.join(', ')}</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{minimalMode ? (
|
||||
<span className="minimal-qty">x{item.quantity}</span>
|
||||
|
||||
@@ -15,10 +15,38 @@ interface PopularResponse {
|
||||
|
||||
let catalogPromise: Promise<CatalogResponse> | null = null;
|
||||
|
||||
function normalizeKnownLocations(value: unknown): string[] | undefined {
|
||||
if (!Array.isArray(value)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const normalized = value
|
||||
.map((location) => (typeof location === 'string' ? location.trim() : ''))
|
||||
.filter((location) => Boolean(location));
|
||||
|
||||
return normalized.length > 0 ? normalized : undefined;
|
||||
}
|
||||
|
||||
function normalizeCatalog(payload: CatalogResponse): CatalogResponse {
|
||||
const normalizedItems = payload.items.map((item) => ({
|
||||
...item,
|
||||
knownLocations: normalizeKnownLocations(item.knownLocations),
|
||||
}));
|
||||
|
||||
const normalizedUpgrades = payload.upgrades.map((upgrade) => {
|
||||
if (Array.isArray(upgrade.levels)) {
|
||||
return upgrade;
|
||||
return {
|
||||
...upgrade,
|
||||
levels: upgrade.levels.map((level) => ({
|
||||
...level,
|
||||
salvage: Array.isArray(level.salvage)
|
||||
? level.salvage.map((salvageItem) => ({
|
||||
...salvageItem,
|
||||
knownLocations: normalizeKnownLocations(salvageItem.knownLocations),
|
||||
}))
|
||||
: [],
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
const legacyUpgrade = upgrade as UpgradeSearchResult & { salvage?: UpgradeSearchResult['levels'][number]['salvage'] };
|
||||
@@ -26,12 +54,21 @@ function normalizeCatalog(payload: CatalogResponse): CatalogResponse {
|
||||
|
||||
return {
|
||||
...upgrade,
|
||||
levels: [{ level: 1, salvage }],
|
||||
levels: [
|
||||
{
|
||||
level: 1,
|
||||
salvage: salvage.map((salvageItem) => ({
|
||||
...salvageItem,
|
||||
knownLocations: normalizeKnownLocations(salvageItem.knownLocations),
|
||||
})),
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
...payload,
|
||||
items: normalizedItems,
|
||||
upgrades: normalizedUpgrades,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
--ui-accent: #caf61d;
|
||||
--color-text-main: #e8edf7;
|
||||
--color-text-input: #f2f6ff;
|
||||
--color-text-muted: #d6dbe8;
|
||||
--color-text-muted: #7f828a;
|
||||
--color-text-done: #8d9bbb;
|
||||
--color-border: #444444;
|
||||
--color-hover: #31373d;
|
||||
@@ -74,6 +74,11 @@ h2 {
|
||||
color: var(--ui-accent);
|
||||
}
|
||||
|
||||
.app-version {
|
||||
font-size: 0.35em;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
label {
|
||||
color: var(--ui-accent);
|
||||
}
|
||||
@@ -257,6 +262,7 @@ input[type='number'] {
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
overflow: visible;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.result-name {
|
||||
@@ -322,7 +328,7 @@ input[type='number'] {
|
||||
padding: 0.65rem;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto auto;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
background: var(--ui-dark-elevated);
|
||||
cursor: pointer;
|
||||
@@ -351,6 +357,7 @@ input[type='number'] {
|
||||
font-weight: 700;
|
||||
min-width: 52px;
|
||||
text-align: right;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.todo-item.highlighted {
|
||||
@@ -359,12 +366,28 @@ input[type='number'] {
|
||||
|
||||
.item-name {
|
||||
overflow-wrap: anywhere;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.item-main {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
gap: 0.2rem;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.item-locations {
|
||||
overflow-wrap: anywhere;
|
||||
font-size: 0.72rem;
|
||||
color: var(--color-text-muted);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.quantity-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.quantity-field input {
|
||||
@@ -400,6 +423,10 @@ input[type='number'] {
|
||||
color: var(--color-text-done);
|
||||
}
|
||||
|
||||
.todo-item.done .item-locations {
|
||||
color: var(--color-text-done);
|
||||
}
|
||||
|
||||
.todo-item.done .minimal-qty,
|
||||
.todo-item.done .quantity-field input {
|
||||
text-decoration: line-through;
|
||||
@@ -434,6 +461,7 @@ input[type='number'] {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.delete:hover {
|
||||
|
||||
@@ -4,6 +4,7 @@ export interface SearchItem {
|
||||
name: string;
|
||||
iconUrl: string;
|
||||
rarity?: string;
|
||||
knownLocations?: string[];
|
||||
}
|
||||
|
||||
export interface UpgradeSalvageItem {
|
||||
@@ -12,6 +13,7 @@ export interface UpgradeSalvageItem {
|
||||
name: string;
|
||||
iconUrl: string;
|
||||
rarity?: string;
|
||||
knownLocations?: string[];
|
||||
}
|
||||
|
||||
export interface UpgradeLevel {
|
||||
@@ -38,6 +40,7 @@ export interface TodoItem {
|
||||
name: string;
|
||||
iconUrl: string;
|
||||
rarity?: string;
|
||||
knownLocations?: string[];
|
||||
quantity: number;
|
||||
completed: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user