diff --git a/components/Info.js b/components/Info.js index a8f2a26..abfc721 100644 --- a/components/Info.js +++ b/components/Info.js @@ -1,25 +1,34 @@ // @ts-check -import { dict, layers } from '../data/dict.js'; +/** + * @typedef {import('../data/types.js').LocationData} LocationData + */ + +import { dict } from '../data/dict.js'; import { furigana } from '../js/furigana.js'; import { state } from '../js/state.js'; import { replaceSpecialChars } from '../js/utils.js'; import van from '../lib/van.js'; +import { Picture } from './Picture.js'; const { a, div, img } = van.tags /** - * @param {import('../data/regions.js').Name} name + * @param {LocationData} data * @returns {HTMLElement} */ -const InfoData = (name) => div( - { - id: 'info-data', - }, - Flag(), - furigana(name), - div({ class: 'wikipedia' }, a({ href: `https://ja.wikipedia.org/wiki/${name.ja}`, target: '_blank' }, furigana(dict.wikipedia))), -); +const InfoData = (data) => { + const { name, picture } = data; + return div( + { + id: 'info-data', + }, + Flag(), + furigana(name), + Wikipedia(name), + picture && Picture(data), + ); +} const InfoTooltip = div( { @@ -28,30 +37,42 @@ const InfoTooltip = div( furigana(dict.toolTip), ); -const flagUrl = () => { +export const filePath = (prefix = '') => { const { municipality, city, prefecture } = state; if (municipality.val) { - return `./img/tokyo/${replaceSpecialChars(municipality.val.name.en)}.svg`; + return `tokyo/${prefix + replaceSpecialChars(municipality.val.name.en)}`; } if (city.val) { - return `./img/city/${replaceSpecialChars(city.val.name.en)},${replaceSpecialChars(prefecture.val.name.en)}.svg`; + return `city/${prefix + replaceSpecialChars(city.val.name.en)},${replaceSpecialChars(prefecture.val.name.en)}`; } if (prefecture.val) { - return `./img/prefecture/${replaceSpecialChars(prefecture.val.name.en)}.svg`; + return `prefecture/${prefix + replaceSpecialChars(prefecture.val.name.en)}`; } return null; } const Flag = () => { - const src = flagUrl(); - - if (!src) { + if (!filePath()) { return null; } + const src = `./img/${filePath()}.svg`; return div({ class: 'flag' }, img({ src })) } +const Wikipedia = (name) => div( + { + class: 'wikipedia' + }, + a( + { + href: `https://ja.wikipedia.org/wiki/${name.ja}`, + target: '_blank' + }, + furigana(dict.wikipedia), + ), +); + export const InfoElm = () => { const data = state.municipality.val || state.city.val || state.prefecture.val || state.region.val || null; @@ -60,6 +81,6 @@ export const InfoElm = () => { id: 'info', class: (data) ? 'data' : 'tooltip', }, - data ? InfoData(data.name) : InfoTooltip, + data ? InfoData(data) : InfoTooltip, ) }; diff --git a/components/Modal.js b/components/Modal.js new file mode 100644 index 0000000..a2bc39f --- /dev/null +++ b/components/Modal.js @@ -0,0 +1,16 @@ +// @ts-check + +import { state } from '../js/state.js'; +import van from '../lib/van.js'; + +const { div } = van.tags; + +export const ModalElm = () => div( + { + id: 'modal', + onclick: () => { + state.modal.val = null; + }, + }, + state.modal.val +); diff --git a/components/Picture.js b/components/Picture.js new file mode 100644 index 0000000..c1033b8 --- /dev/null +++ b/components/Picture.js @@ -0,0 +1,56 @@ +// @ts-check + +/** + * @typedef {import('../data/types.js').LocationData} LocationData + */ + +import { furigana } from '../js/furigana.js'; +import { state } from '../js/state.js'; +import van from '../lib/van.js'; +import { filePath } from './Info.js'; + +const { div, img, h6 } = van.tags + +const picturePath = (prefix = '') => `https://static-nihon.jp7.io/upload/${filePath(prefix)}.webp`; + +/** + * @param {LocationData} data + * @returns {HTMLElement} + */ +export const PictureModal = (data) => div( + { + id: 'picture-modal', + }, + div({ class: 'modal-content' }, + h6( + {}, + furigana(data.name), + div({ class: 'close' }, '╳') + ), + img( + { + src: picturePath(), + } + ), + ) +); + +/** + * @param {LocationData} data + * @returns {HTMLElement} + */ +export const Picture = (data) => { + return div( + { + class: 'picture' + }, + img( + { + src: picturePath('thumb/'), + onclick: () => { + state.modal.val = PictureModal(data); + } + } + ), + ); +} diff --git a/components/index.js b/components/index.js index 56770cc..98ffd41 100644 --- a/components/index.js +++ b/components/index.js @@ -10,13 +10,14 @@ import { LayersAndFillElm } from './LayersAndFill/index.js'; import { ShurikenElm } from './Shuriken.js'; import { MapElm } from './Map/index.js'; import { SvgSourcesElm } from './SvgSources.js'; +import { ModalElm } from './Modal.js'; const { div } = van.tags; export const Root = div( { id: 'root', - class: () => `fillmode-${toId(state.fillmode.val.en)}`, + class: () => `fillmode-${toId(state.fillmode.val.en)} ${state.modal.val ? 'modal' : ''}`, }, TitleElm, LegendElm, @@ -25,4 +26,5 @@ export const Root = div( ShurikenElm, MapElm, SvgSourcesElm, + () => ModalElm(), ); diff --git a/css/index.css b/css/index.css index 9897463..a7e203c 100644 --- a/css/index.css +++ b/css/index.css @@ -15,8 +15,16 @@ --external-margin: 2em; } -body { +div { + box-sizing: border-box; +} + +html, body, #root { width: 100%; + height: 100%; +} + +body { font-family: Arial, Helvetica, sans-serif; font-size: 1vw; margin: 0; @@ -33,3 +41,20 @@ a { text-decoration: none; color: #000; } + +#modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.8); + z-index: 1000; + display: none; +} + +#root.modal #modal { + display: flex !important; + align-items: center; + justify-content: center; +} diff --git a/css/info.css b/css/info.css index b88149d..a05e5af 100644 --- a/css/info.css +++ b/css/info.css @@ -39,21 +39,34 @@ } #info .flag, -#info img { +#info .flag img, +#info .picture { width: 3em; height: 2em; } -#info .flag { +#info .flag, +#info .picture { border: 0.05em solid #ccc; border-radius: 0.2em; } -#info img { +#info .flag img, +#info .picture img { border-radius: 0.15em; display: block; } +#info .picture { + margin-right: -0.4em; +} + +#info .picture img { + width: 100%; + height: 100%; + object-fit: cover; +} + .wikipedia { display: flex; align-items: center; @@ -67,3 +80,37 @@ text-decoration: none; font-size: 0.4em; } + +#picture-modal { + width: 70%; + height: 90%; +} + +#picture-modal .modal-content { + width: 100%; + height: 100%; + border: 1px solid #666; + border-radius: 0.5em; + overflow: hidden; + background: #fff; +} + +#picture-modal img { + width: 100%; + height: 100%; + object-fit: cover; +} + +#picture-modal h6 { + background: rgba(255, 255, 255, 0.9); + font-size: 2em; + color: #000; + margin: 0; + padding: 0.4em; + display: flex; + justify-content: space-between; +} + +#picture-modal h6 .close { + cursor: pointer; +} diff --git a/css/mobile.css b/css/mobile.css index b737779..df753a5 100644 --- a/css/mobile.css +++ b/css/mobile.css @@ -56,6 +56,7 @@ #shuriken { font-size: 0.7vh !important; + padding-bottom: 14em !important; } svg text { @@ -83,7 +84,8 @@ } #info .flag, - #info img { + #info .flag img, + #info .picture { width: 4.5em; height: 3em; } @@ -99,4 +101,9 @@ #tokyo:not(.Tokyo) svg { width: 120% !important; } + + #picture-modal { + width: 90%; + height: 70%; + } } diff --git a/data/regions.js b/data/regions.js index a52e849..4efca28 100644 --- a/data/regions.js +++ b/data/regions.js @@ -26,6 +26,7 @@ * @property {Location} location * @property {boolean=} bottom * @property {Prefecture=} prefecture + * @property {boolean=} picture */ /** @@ -36,6 +37,7 @@ * @property {City[]} cities * @property {string=} textAnchor * @property {Region=} region + * @property {boolean=} picture */ /** @@ -44,6 +46,7 @@ * @property {Name} name * @property {Prefecture[]} prefectures * @property {Zoom} zoom + * @property {boolean=} picture */ export const cityType = { @@ -344,6 +347,7 @@ export const regions = [ furigana: ['とう', 'きょう', 'と'] }, location: { lat: 35.6764225, lng: 139.650027 }, + picture: true, cities: [ { name: { @@ -353,6 +357,7 @@ export const regions = [ }, types: [cityType.capital, cityType.nationalCapital, cityType.favorite], location: { lat: 35.6764225, lng: 139.650027 }, + picture: true, } ], }, @@ -373,6 +378,7 @@ export const regions = [ }, types: [cityType.capital], location: { lat: 35.443707, lng: 139.6380256 }, + picture: true, } ] } @@ -479,6 +485,7 @@ export const regions = [ furigana: ['やま', 'なし', 'けん'] }, location: { lat: 35.6635113, lng: 138.6388879 }, + picture: true, cities: [ { name: { @@ -557,7 +564,7 @@ export const regions = [ }, types: [cityType.favorite], location: { lat: 34.6796171, lng: 138.9451459 }, - + picture: true, } ] }, @@ -624,6 +631,7 @@ export const regions = [ furigana: ['し', 'が', 'けん'] }, location: { lat: 35.3292014, lng: 136.0563212 }, + picture: true, cities: [ { name: { @@ -653,6 +661,7 @@ export const regions = [ }, types: [cityType.capital, cityType.favorite], location: { lat: 35.1566609, lng: 135.5251982 }, + picture: true, } ], }, @@ -674,6 +683,7 @@ export const regions = [ types: [cityType.capital, cityType.favorite], location: { lat: 34.6413315, lng: 135.5629394 }, bottom: true, + picture: true, } ], }, diff --git a/data/tokyo.js b/data/tokyo.js index 750e25b..3d1daa8 100644 --- a/data/tokyo.js +++ b/data/tokyo.js @@ -11,6 +11,7 @@ * @property {string} subprefecture * @property {string} population * @property {string} code + * @property {boolean=} picture */ export const municipalityType = { @@ -46,7 +47,7 @@ export const municipalityType = { const { ku, shi, machi, mura } = municipalityType; -/* @type {Municipality[]} */ +/** @type {Municipality[]} */ export const tokyo = [ { name: { @@ -57,7 +58,8 @@ export const tokyo = [ type: ku, subprefecture: '—', population: '59.441', - code: '13101' + code: '13101', + picture: true, }, { name: { @@ -68,7 +70,8 @@ export const tokyo = [ type: ku, subprefecture: '—', population: '147.62', - code: '13102' + code: '13102', + picture: true, }, { name: { @@ -79,7 +82,8 @@ export const tokyo = [ type: ku, subprefecture: '—', population: '248.071', - code: '13103' + code: '13103', + picture: true, }, { name: { @@ -90,7 +94,8 @@ export const tokyo = [ type: ku, subprefecture: '—', population: '339.211', - code: '13104' + code: '13104', + picture: true, }, { name: { @@ -112,7 +117,8 @@ export const tokyo = [ type: ku, subprefecture: '—', population: '200.486', - code: '13106' + code: '13106', + picture: true, }, { name: { @@ -189,7 +195,8 @@ export const tokyo = [ type: ku, subprefecture: '—', population: '227.85', - code: '13113' + code: '13113', + picture: true, }, { name: { @@ -343,7 +350,8 @@ export const tokyo = [ type: shi, subprefecture: '—', population: '189.168', - code: '13204' + code: '13204', + picture: true, }, { name: { diff --git a/data/types.js b/data/types.js new file mode 100644 index 0000000..864e24d --- /dev/null +++ b/data/types.js @@ -0,0 +1,12 @@ +// @ts-check + +/** + * @typedef {import('./regions').Name} Name + * @typedef {import('./regions').Region} Region + * @typedef {import('./regions').Prefecture} Prefecture + * @typedef {import('./regions').City} City + * @typedef {import('./tokyo').Municipality} Municipality + * @typedef {Region | Prefecture | City | Municipality} LocationData + */ + +export { }; diff --git a/js/state.js b/js/state.js index 86001fc..880c8e5 100644 --- a/js/state.js +++ b/js/state.js @@ -26,6 +26,7 @@ import van from '../lib/van.js'; * @property {State} mapPrefecturesReady * @property {State} mapCitiesReady * @property {State} mapTokyoReady + * @property {State} modal */ /** @type {AppState} */ @@ -43,4 +44,5 @@ export const state = { mapPrefecturesReady: van.state(false), mapCitiesReady: van.state(false), mapTokyoReady: van.state(false), + modal: van.state(null), } diff --git a/js/utils.js b/js/utils.js index 13cacbd..9fab962 100644 --- a/js/utils.js +++ b/js/utils.js @@ -52,13 +52,13 @@ export function compareIds(a, b) { } /** - * @param {import('../data/regions').Region[]} data + * @param {Region[]} data * @param {string | null} filter */ const filteredData = (data, filter) => data.filter(region => !filter || region.name.en === filter); /** - * @param {import('../data/regions').Region[]} data + * @param {Region[]} data * @param {string | null} filter */ export function parseData(data, filter = null) { @@ -78,7 +78,7 @@ export function parseData(data, filter = null) { } /** - * @param {import('../data/regions').Region[]} data + * @param {Region[]} data * @param {string | null} filter */ export function parseDataForPrefectures(data, filter = null) { @@ -100,7 +100,7 @@ export function parseDataForPrefectures(data, filter = null) { } /** - * @param {import('../data/regions').Region[]} data + * @param {Region[]} data * @param {string | null} filter */ export function parseDataForCities(data, filter = null) {