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:
61
.gitea/workflows/redeploy-compose.yml
Normal file
61
.gitea/workflows/redeploy-compose.yml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: Redeploy Docker Compose
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
redeploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
|
||||||
|
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }}
|
||||||
|
DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
|
||||||
|
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
|
||||||
|
DEPLOY_BRANCH: main
|
||||||
|
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
||||||
|
steps:
|
||||||
|
- name: Validate required secrets
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
: "${DEPLOY_PORT:=22}"
|
||||||
|
missing=0
|
||||||
|
for key in DEPLOY_HOST DEPLOY_USER DEPLOY_PATH SSH_PRIVATE_KEY; do
|
||||||
|
if [ -z "${!key:-}" ]; then
|
||||||
|
echo "Missing required secret: $key"
|
||||||
|
missing=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ "$missing" -ne 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Configure SSH key
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
: "${DEPLOY_PORT:=22}"
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
chmod 700 ~/.ssh
|
||||||
|
printf '%s\n' "$SSH_PRIVATE_KEY" > ~/.ssh/id_ed25519
|
||||||
|
chmod 600 ~/.ssh/id_ed25519
|
||||||
|
ssh-keyscan -p "$DEPLOY_PORT" -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts
|
||||||
|
chmod 644 ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
- name: Redeploy on remote host
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
: "${DEPLOY_PORT:=22}"
|
||||||
|
ssh -p "$DEPLOY_PORT" "$DEPLOY_USER@$DEPLOY_HOST" \
|
||||||
|
"DEPLOY_PATH='$DEPLOY_PATH' DEPLOY_BRANCH='$DEPLOY_BRANCH' bash -se" <<'EOF'
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$DEPLOY_PATH"
|
||||||
|
git fetch origin "$DEPLOY_BRANCH"
|
||||||
|
git checkout "$DEPLOY_BRANCH"
|
||||||
|
git pull --ff-only origin "$DEPLOY_BRANCH"
|
||||||
|
docker compose pull
|
||||||
|
docker compose up -d --build --remove-orphans
|
||||||
|
EOF
|
||||||
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.5.1 - 2026-03-26
|
||||||
|
- Added Gitea Actions workflow for automatic Docker Compose redeploy on `main` updates:
|
||||||
|
- New workflow file: `.gitea/workflows/redeploy-compose.yml`.
|
||||||
|
- Uses SSH to connect to deployment host and run `git pull` + `docker compose up -d --build --remove-orphans`.
|
||||||
|
- Supports optional `DEPLOY_PORT` secret (defaults to `22`).
|
||||||
|
- Updated `README.md` with Gitea deployment workflow setup and required secrets.
|
||||||
|
|
||||||
## 0.5.0 - 2026-03-26
|
## 0.5.0 - 2026-03-26
|
||||||
- Added anonymous backend visit logging in SQLite:
|
- Added anonymous backend visit logging in SQLite:
|
||||||
- New `visit_logs` table records timestamp, hashed visitor id, and country metadata.
|
- New `visit_logs` table records timestamp, hashed visitor id, and country metadata.
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -56,3 +56,21 @@ A React + TypeScript app with a local Node + SQLite backend for planning what to
|
|||||||
```bash
|
```bash
|
||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Gitea Auto Deploy (Docker Compose)
|
||||||
|
When `main` is updated, Gitea Actions can redeploy your Docker Compose stack using:
|
||||||
|
- Workflow file: `.gitea/workflows/redeploy-compose.yml`
|
||||||
|
- Trigger: push to `main`
|
||||||
|
- Remote commands run over SSH:
|
||||||
|
- `git fetch`
|
||||||
|
- `git checkout main`
|
||||||
|
- `git pull --ff-only`
|
||||||
|
- `docker compose pull`
|
||||||
|
- `docker compose up -d --build --remove-orphans`
|
||||||
|
|
||||||
|
Set these repository secrets in Gitea:
|
||||||
|
- `DEPLOY_HOST`: server hostname or IP
|
||||||
|
- `DEPLOY_USER`: SSH username on the deployment server
|
||||||
|
- `DEPLOY_PATH`: absolute path to this repo on the server
|
||||||
|
- `SSH_PRIVATE_KEY`: private key for SSH auth (matching an authorized public key on the server)
|
||||||
|
- `DEPLOY_PORT` (optional): SSH port, defaults to `22`
|
||||||
|
|||||||
@@ -422,6 +422,17 @@ function getRarity(raw) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getKnownLocations(raw) {
|
||||||
|
const value = raw.known_locations;
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
.map((location) => normalizeName(location))
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
function getItemIconUrl(iconPath, slug) {
|
function getItemIconUrl(iconPath, slug) {
|
||||||
const itemImageBase = 'https://items.marathondb.gg/images/items';
|
const itemImageBase = 'https://items.marathondb.gg/images/items';
|
||||||
const apiBase = 'https://helpbot.marathondb.gg';
|
const apiBase = 'https://helpbot.marathondb.gg';
|
||||||
@@ -554,6 +565,7 @@ function buildUpgradeResults(factionUpgrades, itemsBySlug) {
|
|||||||
name: entry.item.name,
|
name: entry.item.name,
|
||||||
iconUrl: entry.item.iconUrl,
|
iconUrl: entry.item.iconUrl,
|
||||||
rarity: entry.item.rarity,
|
rarity: entry.item.rarity,
|
||||||
|
knownLocations: entry.item.knownLocations,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
mappedLevels.push({
|
mappedLevels.push({
|
||||||
@@ -602,12 +614,14 @@ function buildCatalog(itemsPayload, factionUpgradesPayload, updatedAtMs) {
|
|||||||
const slug = getSlug(row);
|
const slug = getSlug(row);
|
||||||
const iconPath = getIconPath(row);
|
const iconPath = getIconPath(row);
|
||||||
const rarity = getRarity(row);
|
const rarity = getRarity(row);
|
||||||
|
const knownLocations = getKnownLocations(row);
|
||||||
return {
|
return {
|
||||||
id: slug,
|
id: slug,
|
||||||
slug,
|
slug,
|
||||||
name,
|
name,
|
||||||
iconUrl: getItemIconUrl(iconPath, slug),
|
iconUrl: getItemIconUrl(iconPath, slug),
|
||||||
rarity,
|
rarity,
|
||||||
|
knownLocations,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|||||||
@@ -25,6 +25,18 @@ function isUpgradeResult(result: SearchResult): result is UpgradeSearchResult {
|
|||||||
return 'isUpgrade' in result;
|
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({
|
function ItemIcon({
|
||||||
src,
|
src,
|
||||||
alt,
|
alt,
|
||||||
@@ -85,6 +97,9 @@ function loadTodoItems(): TodoItem[] {
|
|||||||
typeof row.name === 'string' &&
|
typeof row.name === 'string' &&
|
||||||
typeof row.iconUrl === 'string' &&
|
typeof row.iconUrl === 'string' &&
|
||||||
(row.rarity === undefined || typeof row.rarity === '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.quantity === 'number' &&
|
||||||
typeof row.completed === 'boolean'
|
typeof row.completed === 'boolean'
|
||||||
);
|
);
|
||||||
@@ -92,6 +107,7 @@ function loadTodoItems(): TodoItem[] {
|
|||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
rarity: typeof item.rarity === 'string' ? item.rarity : undefined,
|
rarity: typeof item.rarity === 'string' ? item.rarity : undefined,
|
||||||
|
knownLocations: normalizeKnownLocations(item.knownLocations),
|
||||||
quantity: Math.max(1, Math.floor(item.quantity)),
|
quantity: Math.max(1, Math.floor(item.quantity)),
|
||||||
}));
|
}));
|
||||||
} catch {
|
} catch {
|
||||||
@@ -177,6 +193,7 @@ function decodeListFromUrl(encoded: string, allItems: SearchItem[]): TodoItem[]
|
|||||||
name: match.name,
|
name: match.name,
|
||||||
iconUrl: match.iconUrl,
|
iconUrl: match.iconUrl,
|
||||||
rarity: match.rarity,
|
rarity: match.rarity,
|
||||||
|
knownLocations: match.knownLocations,
|
||||||
quantity,
|
quantity,
|
||||||
completed,
|
completed,
|
||||||
};
|
};
|
||||||
@@ -212,11 +229,14 @@ export default function App() {
|
|||||||
return;
|
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) =>
|
setTodoItems((prevItems) =>
|
||||||
prevItems.map((item) => ({
|
prevItems.map((item) => ({
|
||||||
...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);
|
const existing = prevItems.find((entry) => entry.slug === item.slug);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
return prevItems.map((entry) =>
|
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,
|
name: item.name,
|
||||||
iconUrl: item.iconUrl,
|
iconUrl: item.iconUrl,
|
||||||
rarity: item.rarity,
|
rarity: item.rarity,
|
||||||
|
knownLocations: item.knownLocations,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
completed: false,
|
completed: false,
|
||||||
},
|
},
|
||||||
@@ -398,6 +426,8 @@ export default function App() {
|
|||||||
nextItems[existingIndex] = {
|
nextItems[existingIndex] = {
|
||||||
...nextItems[existingIndex],
|
...nextItems[existingIndex],
|
||||||
quantity: nextItems[existingIndex].quantity + amount,
|
quantity: nextItems[existingIndex].quantity + amount,
|
||||||
|
rarity: nextItems[existingIndex].rarity ?? salvageEntry.rarity,
|
||||||
|
knownLocations: nextItems[existingIndex].knownLocations ?? salvageEntry.knownLocations,
|
||||||
};
|
};
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -408,6 +438,7 @@ export default function App() {
|
|||||||
name: salvageEntry.name,
|
name: salvageEntry.name,
|
||||||
iconUrl: salvageEntry.iconUrl,
|
iconUrl: salvageEntry.iconUrl,
|
||||||
rarity: salvageEntry.rarity,
|
rarity: salvageEntry.rarity,
|
||||||
|
knownLocations: salvageEntry.knownLocations,
|
||||||
quantity: amount,
|
quantity: amount,
|
||||||
completed: false,
|
completed: false,
|
||||||
});
|
});
|
||||||
@@ -501,7 +532,9 @@ export default function App() {
|
|||||||
<header>
|
<header>
|
||||||
<div className="header-top">
|
<div className="header-top">
|
||||||
<div>
|
<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}
|
{!minimalMode ? <p>Plan what to loot (or do) in your next Marathon raid.</p> : null}
|
||||||
</div>
|
</div>
|
||||||
{!minimalMode ? (
|
{!minimalMode ? (
|
||||||
@@ -738,12 +771,17 @@ export default function App() {
|
|||||||
<ItemIcon src={item.iconUrl} alt="" size={37} />
|
<ItemIcon src={item.iconUrl} alt="" size={37} />
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span
|
<div className="item-main">
|
||||||
className="item-name"
|
<span
|
||||||
style={getRarityGlowColor(item.rarity) ? { color: getRarityGlowColor(item.rarity)! } : undefined}
|
className="item-name"
|
||||||
>
|
style={getRarityGlowColor(item.rarity) ? { color: getRarityGlowColor(item.rarity)! } : undefined}
|
||||||
{item.name}
|
>
|
||||||
</span>
|
{item.name}
|
||||||
|
</span>
|
||||||
|
{item.knownLocations && item.knownLocations.length > 0 ? (
|
||||||
|
<span className="item-locations">{item.knownLocations.join(', ')}</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
{minimalMode ? (
|
{minimalMode ? (
|
||||||
<span className="minimal-qty">x{item.quantity}</span>
|
<span className="minimal-qty">x{item.quantity}</span>
|
||||||
|
|||||||
@@ -15,10 +15,38 @@ interface PopularResponse {
|
|||||||
|
|
||||||
let catalogPromise: Promise<CatalogResponse> | null = null;
|
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 {
|
function normalizeCatalog(payload: CatalogResponse): CatalogResponse {
|
||||||
|
const normalizedItems = payload.items.map((item) => ({
|
||||||
|
...item,
|
||||||
|
knownLocations: normalizeKnownLocations(item.knownLocations),
|
||||||
|
}));
|
||||||
|
|
||||||
const normalizedUpgrades = payload.upgrades.map((upgrade) => {
|
const normalizedUpgrades = payload.upgrades.map((upgrade) => {
|
||||||
if (Array.isArray(upgrade.levels)) {
|
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'] };
|
const legacyUpgrade = upgrade as UpgradeSearchResult & { salvage?: UpgradeSearchResult['levels'][number]['salvage'] };
|
||||||
@@ -26,12 +54,21 @@ function normalizeCatalog(payload: CatalogResponse): CatalogResponse {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...upgrade,
|
...upgrade,
|
||||||
levels: [{ level: 1, salvage }],
|
levels: [
|
||||||
|
{
|
||||||
|
level: 1,
|
||||||
|
salvage: salvage.map((salvageItem) => ({
|
||||||
|
...salvageItem,
|
||||||
|
knownLocations: normalizeKnownLocations(salvageItem.knownLocations),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...payload,
|
...payload,
|
||||||
|
items: normalizedItems,
|
||||||
upgrades: normalizedUpgrades,
|
upgrades: normalizedUpgrades,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
--ui-accent: #caf61d;
|
--ui-accent: #caf61d;
|
||||||
--color-text-main: #e8edf7;
|
--color-text-main: #e8edf7;
|
||||||
--color-text-input: #f2f6ff;
|
--color-text-input: #f2f6ff;
|
||||||
--color-text-muted: #d6dbe8;
|
--color-text-muted: #7f828a;
|
||||||
--color-text-done: #8d9bbb;
|
--color-text-done: #8d9bbb;
|
||||||
--color-border: #444444;
|
--color-border: #444444;
|
||||||
--color-hover: #31373d;
|
--color-hover: #31373d;
|
||||||
@@ -74,6 +74,11 @@ h2 {
|
|||||||
color: var(--ui-accent);
|
color: var(--ui-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-version {
|
||||||
|
font-size: 0.35em;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
color: var(--ui-accent);
|
color: var(--ui-accent);
|
||||||
}
|
}
|
||||||
@@ -257,6 +262,7 @@ input[type='number'] {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
|
align-self: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result-name {
|
.result-name {
|
||||||
@@ -322,7 +328,7 @@ input[type='number'] {
|
|||||||
padding: 0.65rem;
|
padding: 0.65rem;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr auto auto;
|
grid-template-columns: auto 1fr auto auto;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
background: var(--ui-dark-elevated);
|
background: var(--ui-dark-elevated);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -351,6 +357,7 @@ input[type='number'] {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
min-width: 52px;
|
min-width: 52px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.todo-item.highlighted {
|
.todo-item.highlighted {
|
||||||
@@ -359,12 +366,28 @@ input[type='number'] {
|
|||||||
|
|
||||||
.item-name {
|
.item-name {
|
||||||
overflow-wrap: anywhere;
|
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 {
|
.quantity-field {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.35rem;
|
gap: 0.35rem;
|
||||||
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quantity-field input {
|
.quantity-field input {
|
||||||
@@ -400,6 +423,10 @@ input[type='number'] {
|
|||||||
color: var(--color-text-done);
|
color: var(--color-text-done);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.todo-item.done .item-locations {
|
||||||
|
color: var(--color-text-done);
|
||||||
|
}
|
||||||
|
|
||||||
.todo-item.done .minimal-qty,
|
.todo-item.done .minimal-qty,
|
||||||
.todo-item.done .quantity-field input {
|
.todo-item.done .quantity-field input {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
@@ -434,6 +461,7 @@ input[type='number'] {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
align-self: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete:hover {
|
.delete:hover {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export interface SearchItem {
|
|||||||
name: string;
|
name: string;
|
||||||
iconUrl: string;
|
iconUrl: string;
|
||||||
rarity?: string;
|
rarity?: string;
|
||||||
|
knownLocations?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpgradeSalvageItem {
|
export interface UpgradeSalvageItem {
|
||||||
@@ -12,6 +13,7 @@ export interface UpgradeSalvageItem {
|
|||||||
name: string;
|
name: string;
|
||||||
iconUrl: string;
|
iconUrl: string;
|
||||||
rarity?: string;
|
rarity?: string;
|
||||||
|
knownLocations?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpgradeLevel {
|
export interface UpgradeLevel {
|
||||||
@@ -38,6 +40,7 @@ export interface TodoItem {
|
|||||||
name: string;
|
name: string;
|
||||||
iconUrl: string;
|
iconUrl: string;
|
||||||
rarity?: string;
|
rarity?: string;
|
||||||
|
knownLocations?: string[];
|
||||||
quantity: number;
|
quantity: number;
|
||||||
completed: boolean;
|
completed: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user