fix: UI world map

This commit is contained in:
2026-06-16 22:31:17 +09:00
parent ed415a78a1
commit d614ddb322
5 changed files with 48 additions and 3 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 963 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -7,18 +7,23 @@
let fileInput; let fileInput;
let uploading = $state(false); let uploading = $state(false);
let uploadError = $state('');
function remove(index) { function remove(index) {
onchange(photos.filter((_, i) => i !== index)); onchange(photos.filter((_, i) => i !== index));
uploadError = '';
} }
async function addFiles(e) { async function addFiles(e) {
const files = Array.from(e.currentTarget.files ?? []); const files = Array.from(e.currentTarget.files ?? []);
if (!files.length) return; if (!files.length) return;
uploading = true; uploading = true;
uploadError = '';
try { try {
const urls = await Promise.all(files.map(uploadPhoto)); const urls = await Promise.all(files.map(uploadPhoto));
onchange([...photos, ...urls]); onchange([...photos, ...urls]);
} catch (err) {
uploadError = err?.message ?? 'Upload failed. Check Firebase Storage rules.';
} finally { } finally {
uploading = false; uploading = false;
e.currentTarget.value = ''; e.currentTarget.value = '';
@@ -68,6 +73,10 @@
</button> </button>
</div> </div>
{/if} {/if}
{#if uploadError}
<div class="upload-error">{uploadError}</div>
{/if}
</div> </div>
<style> <style>
@@ -183,4 +192,15 @@
transition: border-color 0.15s, color 0.15s; transition: border-color 0.15s, color 0.15s;
} }
.add-cell:hover { border-color: var(--accent-border); color: var(--accent); } .add-cell:hover { border-color: var(--accent-border); color: var(--accent); }
.upload-error {
font-size: 12px;
color: #ef4444;
background: #fef2f2;
border: 1px solid #fecaca;
border-radius: 6px;
padding: 8px 10px;
line-height: 1.4;
word-break: break-word;
}
</style> </style>

View File

@@ -5,7 +5,7 @@
import worldData from 'world-atlas/countries-50m.json'; import worldData from 'world-atlas/countries-50m.json';
import { get } from 'svelte/store'; import { get } from 'svelte/store';
import { journals } from '../stores/journalStore.js'; import { journals } from '../stores/journalStore.js';
import airplaneImg from '../../assets/airplane.png'; import airplaneImg from '../../assets/airplane-animation.png';
let { onclose, onprogress, mode = 'map', onmodechange } = $props(); let { onclose, onprogress, mode = 'map', onmodechange } = $props();

View File

@@ -6,6 +6,7 @@
import { getSelected, setTotalCount, getFlashing } from '../layout/selection.svelte.js'; import { getSelected, setTotalCount, getFlashing } from '../layout/selection.svelte.js';
import { getUserProfile } from '../auth/userStore.svelte.js'; import { getUserProfile } from '../auth/userStore.svelte.js';
import { nameToId } from '../shared/countries.js'; import { nameToId } from '../shared/countries.js';
import homeIconUrl from '../../assets/home.png';
import crayonCursorUrl from '../../assets/logo-cursor.png'; import crayonCursorUrl from '../../assets/logo-cursor.png';
let { onCountryClick = (_name) => {} } = $props(); let { onCountryClick = (_name) => {} } = $props();
@@ -103,6 +104,28 @@
$effect(updateAllFills); $effect(updateAllFills);
function placeHomeMarker() {
if (!_g || !_pathFn || !_countries) return;
_g.selectAll('.home-marker').remove();
const name = getUserProfile()?.homeCountry;
if (!name) return;
const found = _countries.find(f => f.properties.name === name);
if (!found) return;
const [cx, cy] = _pathFn.centroid(found);
if (isNaN(cx) || isNaN(cy)) return;
const SIZE = 14;
_g.append('image')
.attr('class', 'home-marker')
.attr('href', homeIconUrl)
.attr('x', cx - SIZE / 2)
.attr('y', cy - SIZE / 2)
.attr('width', SIZE)
.attr('height', SIZE)
.style('pointer-events', 'none');
}
$effect(placeHomeMarker);
$effect(() => { $effect(() => {
const flashSet = getFlashing(); const flashSet = getFlashing();
const paths = _paths; // reactive read so effect re-runs when _paths is set const paths = _paths; // reactive read so effect re-runs when _paths is set
@@ -166,7 +189,7 @@
}) })
.on('mousemove', (event) => { .on('mousemove', (event) => {
const [x, y] = d3.pointer(event, frameEl); const [x, y] = d3.pointer(event, frameEl);
tooltip.style('left', (x + 10) + 'px').style('top', (y - 28) + 'px'); tooltip.style('left', (x + 22) + 'px').style('top', (y - 28) + 'px');
}) })
.on('mouseleave', (event, d) => { .on('mouseleave', (event, d) => {
const s = getSelected(); const s = getSelected();
@@ -207,6 +230,7 @@
} }
renderMicrostates(); renderMicrostates();
placeHomeMarker();
const zoom = d3.zoom() const zoom = d3.zoom()
.scaleExtent([1, 32]) .scaleExtent([1, 32])
@@ -230,8 +254,9 @@
fitProjection(projection, width, height); fitProjection(projection, width, height);
const countryPaths = _g.selectAll('path'); const countryPaths = _g.selectAll('path');
countryPaths.attr('d', path); countryPaths.attr('d', path);
updateFill(countryPaths); updateAllFills();
renderMicrostates(); renderMicrostates();
placeHomeMarker();
} }
}); });