@projektemacher/storymapjs 0.10.7 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG +431 -19
- package/README.md +168 -17
- package/dist/assets/demo-bWnOyfUw.js +1 -0
- package/dist/assets/harness-C3JUv5Ii.js +1 -0
- package/dist/assets/harness-multi-XZNex3tb.js +1 -0
- package/dist/assets/harness-support-CjfZIVeb.js +1 -0
- package/dist/assets/index-wHPqGrBS.js +1 -0
- package/dist/assets/main-DQ32hWBM.js +17 -0
- package/dist/context.json +23 -9
- package/dist/css/fonts/files/amatic-sc-cyrillic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/amatic-sc-cyrillic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/amatic-sc-hebrew-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/amatic-sc-hebrew-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/amatic-sc-vietnamese-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/amatic-sc-vietnamese-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-100-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-100-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-200-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-200-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-300-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-500-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-600-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-600-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-800-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-800-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-900-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-900-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-100-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-100-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-200-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-200-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-300-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-500-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-600-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-600-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-800-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-800-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-900-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-900-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-cyrillic-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-100-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-100-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-200-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-200-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-300-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-500-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-600-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-600-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-800-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-800-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-900-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-900-normal.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/bitter-vietnamese-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-arabic-200-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-arabic-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-arabic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-arabic-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-arabic-600-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-arabic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-arabic-800-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-arabic-900-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-200-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-600-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-800-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-900-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-ext-200-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-ext-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-ext-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-ext-600-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-ext-800-normal.woff2 +0 -0
- package/dist/css/fonts/files/cairo-latin-ext-900-normal.woff2 +0 -0
- package/dist/css/fonts/files/dancing-script-vietnamese-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-cyrillic-ext-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-cyrillic-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-cyrillic-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-greek-ext-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-greek-ext-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-greek-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-greek-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-vietnamese-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/eb-garamond-vietnamese-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-cyrillic-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-cyrillic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-cyrillic-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-cyrillic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-cyrillic-ext-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-cyrillic-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-cyrillic-ext-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-cyrillic-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-greek-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-greek-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-greek-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-greek-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-greek-ext-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-greek-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-greek-ext-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-greek-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-vietnamese-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-vietnamese-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-vietnamese-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/gentium-book-plus-vietnamese-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/old-standard-tt-cyrillic-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/old-standard-tt-cyrillic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/old-standard-tt-cyrillic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/old-standard-tt-cyrillic-ext-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/old-standard-tt-cyrillic-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/old-standard-tt-cyrillic-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/old-standard-tt-vietnamese-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/old-standard-tt-vietnamese-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/old-standard-tt-vietnamese-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-cyrillic-ext-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-cyrillic-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-cyrillic-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-greek-ext-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-greek-ext-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-greek-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-greek-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-hebrew-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-hebrew-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-vietnamese-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/open-sans-vietnamese-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-cyrillic-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-cyrillic-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-cyrillic-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-cyrillic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-cyrillic-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-cyrillic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-cyrillic-900-italic.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-cyrillic-900-normal.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-vietnamese-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-vietnamese-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-vietnamese-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-vietnamese-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-vietnamese-900-italic.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-sc-vietnamese-900-normal.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-vietnamese-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/playfair-display-vietnamese-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-cyrillic-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-cyrillic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-cyrillic-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-cyrillic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-cyrillic-ext-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-cyrillic-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-cyrillic-ext-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-cyrillic-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-narrow-cyrillic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-narrow-cyrillic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-narrow-cyrillic-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-sans-narrow-cyrillic-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-serif-cyrillic-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/pt-serif-cyrillic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-serif-cyrillic-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/pt-serif-cyrillic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-serif-cyrillic-ext-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/pt-serif-cyrillic-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/pt-serif-cyrillic-ext-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/pt-serif-cyrillic-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/raleway-cyrillic-ext-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/raleway-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/raleway-cyrillic-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/raleway-cyrillic-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/raleway-vietnamese-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/raleway-vietnamese-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/roboto-slab-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/roboto-slab-cyrillic-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/roboto-slab-greek-ext-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/roboto-slab-greek-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/roboto-slab-vietnamese-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-300-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-500-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-ext-300-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-ext-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-ext-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-ext-500-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-ext-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-ext-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-cyrillic-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-300-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-500-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-ext-300-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-ext-300-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-ext-400-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-ext-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-ext-500-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-ext-500-normal.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-ext-700-italic.woff2 +0 -0
- package/dist/css/fonts/files/ubuntu-greek-ext-700-normal.woff2 +0 -0
- package/dist/css/fonts/files/unica-one-vietnamese-400-normal.woff2 +0 -0
- package/dist/css/fonts/files/vollkorn-cyrillic-ext-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/vollkorn-cyrillic-ext-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/vollkorn-cyrillic-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/vollkorn-cyrillic-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/vollkorn-greek-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/vollkorn-greek-wght-normal.woff2 +0 -0
- package/dist/css/fonts/files/vollkorn-vietnamese-wght-italic.woff2 +0 -0
- package/dist/css/fonts/files/vollkorn-vietnamese-wght-normal.woff2 +0 -0
- package/dist/css/fonts/font.abril-droidsans.css +3 -0
- package/dist/css/fonts/font.amatic-andika.css +57 -0
- package/dist/css/fonts/font.bitter-raleway.css +111 -0
- package/dist/css/fonts/font.cairo.css +312 -0
- package/dist/css/fonts/font.clicker-garamond.css +93 -0
- package/dist/css/fonts/font.dancing-ledger.css +12 -0
- package/dist/css/fonts/font.default.css +183 -0
- package/dist/css/fonts/font.fjalla-average.css +3 -0
- package/dist/css/fonts/font.georgia-helvetica.css +3 -0
- package/dist/css/fonts/font.knightlab.css +642 -0
- package/dist/css/fonts/font.lustria-lato.css +3 -0
- package/dist/css/fonts/font.medula-lato.css +3 -0
- package/dist/css/fonts/font.oldstandard.css +84 -0
- package/dist/css/fonts/font.opensans-gentiumbook.css +183 -0
- package/dist/css/fonts/font.playfair-faunaone.css +39 -0
- package/dist/css/fonts/font.playfair.css +147 -0
- package/dist/css/fonts/font.pt.css +183 -0
- package/dist/css/fonts/font.roboto-megrim.css +3 -0
- package/dist/css/fonts/font.rufina-sintony.css +3 -0
- package/dist/css/fonts/font.ubuntu.css +291 -0
- package/dist/css/fonts/font.unicaone-vollkorn.css +84 -0
- package/dist/css/storymap.css +1 -1
- package/dist/demo.html +3 -2
- package/dist/docs/api/.nojekyll +1 -0
- package/dist/docs/api/assets/custom.css +47 -0
- package/dist/docs/api/assets/hierarchy.js +1 -0
- package/dist/docs/api/assets/highlight.css +57 -0
- package/dist/docs/api/assets/icons.js +18 -0
- package/dist/docs/api/assets/icons.svg +1 -0
- package/dist/docs/api/assets/main.js +60 -0
- package/dist/docs/api/assets/navigation.js +1 -0
- package/dist/docs/api/assets/search.js +1 -0
- package/dist/docs/api/assets/style.css +1648 -0
- package/dist/docs/api/classes/StoryMap.html +2112 -0
- package/dist/docs/api/functions/MediaType.html +51 -0
- package/dist/docs/api/functions/isPresentation3Collection.html +47 -0
- package/dist/docs/api/functions/isPresentation3Manifest.html +53 -0
- package/dist/docs/api/functions/isSlideshowCollection.html +51 -0
- package/dist/docs/api/functions/loadCSS.html +63 -0
- package/dist/docs/api/functions/manifestToStorymapData.html +49 -0
- package/dist/docs/api/functions/setLanguage.html +54 -0
- package/dist/docs/api/functions/slideshowToStorymapData.html +51 -0
- package/dist/docs/api/functions/slideshowVersion.html +48 -0
- package/dist/docs/api/functions/storymapToManifest.html +57 -0
- package/dist/docs/api/functions/validateStorymap.html +53 -0
- package/dist/docs/api/functions/validateStorymapAndReport.html +54 -0
- package/dist/docs/api/hierarchy.html +28 -0
- package/dist/docs/api/index.html +300 -0
- package/dist/docs/api/interfaces/AnimationHandle.html +60 -0
- package/dist/docs/api/interfaces/ImagereadyPayload.html +71 -0
- package/dist/docs/api/interfaces/LanguageEntry.html +102 -0
- package/dist/docs/api/interfaces/LatLngLiteral.html +73 -0
- package/dist/docs/api/interfaces/LoadOptions.html +59 -0
- package/dist/docs/api/interfaces/MapEvents.html +116 -0
- package/dist/docs/api/interfaces/MapMarkerEvents.html +66 -0
- package/dist/docs/api/interfaces/MarkerEventPayload.html +57 -0
- package/dist/docs/api/interfaces/MediaEvents.html +78 -0
- package/dist/docs/api/interfaces/MenuBarEvents.html +114 -0
- package/dist/docs/api/interfaces/MessageEvents.html +72 -0
- package/dist/docs/api/interfaces/OlLayer.html +1400 -0
- package/dist/docs/api/interfaces/OlMap.html +1656 -0
- package/dist/docs/api/interfaces/OlProjection.html +332 -0
- package/dist/docs/api/interfaces/OlSource.html +784 -0
- package/dist/docs/api/interfaces/OlTileLayer.html +1497 -0
- package/dist/docs/api/interfaces/OlVectorLayer.html +1559 -0
- package/dist/docs/api/interfaces/OlView.html +1958 -0
- package/dist/docs/api/interfaces/SlideBackgroundState.html +66 -0
- package/dist/docs/api/interfaces/SlideEvents.html +78 -0
- package/dist/docs/api/interfaces/SlideNavEvents.html +72 -0
- package/dist/docs/api/interfaces/SlideshowPlayerSettings.html +182 -0
- package/dist/docs/api/interfaces/SlideshowToStorymapResult.html +60 -0
- package/dist/docs/api/interfaces/SlideshowTranslatorOptions.html +65 -0
- package/dist/docs/api/interfaces/SlideshowWarning.html +72 -0
- package/dist/docs/api/interfaces/StoryMapEvents.html +146 -0
- package/dist/docs/api/interfaces/StoryMapMarker.html +763 -0
- package/dist/docs/api/interfaces/StorySliderEvents.html +102 -0
- package/dist/docs/api/interfaces/StorymapBasemap.html +69 -0
- package/dist/docs/api/interfaces/StorymapData.html +77 -0
- package/dist/docs/api/interfaces/StorymapDataWrapper.html +60 -0
- package/dist/docs/api/interfaces/StorymapDocument.html +68 -0
- package/dist/docs/api/interfaces/StorymapError.html +63 -0
- package/dist/docs/api/interfaces/StorymapGeoreference.html +93 -0
- package/dist/docs/api/interfaces/StorymapGeoreferenceBody.html +74 -0
- package/dist/docs/api/interfaces/StorymapGroundControlPoint.html +69 -0
- package/dist/docs/api/interfaces/StorymapLanguageMap.html +57 -0
- package/dist/docs/api/interfaces/StorymapManifest.html +118 -0
- package/dist/docs/api/interfaces/StorymapManifestAnnotation.html +137 -0
- package/dist/docs/api/interfaces/StorymapManifestBackground.html +81 -0
- package/dist/docs/api/interfaces/StorymapManifestCanvas.html +188 -0
- package/dist/docs/api/interfaces/StorymapManifestContentResource.html +97 -0
- package/dist/docs/api/interfaces/StorymapManifestFeatureCollection.html +82 -0
- package/dist/docs/api/interfaces/StorymapManifestGeoreferencing.html +87 -0
- package/dist/docs/api/interfaces/StorymapManifestImageService.html +69 -0
- package/dist/docs/api/interfaces/StorymapManifestRange.html +75 -0
- package/dist/docs/api/interfaces/StorymapManifestService.html +77 -0
- package/dist/docs/api/interfaces/StorymapManifestSpecificResource.html +69 -0
- package/dist/docs/api/interfaces/StorymapManifestStatement.html +63 -0
- package/dist/docs/api/interfaces/StorymapMapOptions.html +92 -0
- package/dist/docs/api/interfaces/StorymapOptions.html +734 -0
- package/dist/docs/api/interfaces/StorymapOverlayLayer.html +157 -0
- package/dist/docs/api/interfaces/StorymapSlide.html +186 -0
- package/dist/docs/api/interfaces/StorymapSlideBackground.html +72 -0
- package/dist/docs/api/interfaces/StorymapSlideLocation.html +161 -0
- package/dist/docs/api/interfaces/StorymapSlideMarker.html +103 -0
- package/dist/docs/api/interfaces/StorymapSlideMedia.html +149 -0
- package/dist/docs/api/interfaces/StorymapSlideNarration.html +117 -0
- package/dist/docs/api/interfaces/StorymapSlideText.html +72 -0
- package/dist/docs/api/interfaces/StorymapTilejson.html +105 -0
- package/dist/docs/api/interfaces/SwipableEvents.html +102 -0
- package/dist/docs/api/interfaces/TextEvents.html +66 -0
- package/dist/docs/api/media/es.json +31 -0
- package/dist/docs/api/media/events.md +66 -0
- package/dist/docs/api/media/migration-from-knightlab.md +399 -0
- package/dist/docs/api/media/slideshow.md +109 -0
- package/dist/docs/api/media/storymap-as-iiif-manifest.md +1007 -0
- package/dist/docs/api/modules.html +50 -0
- package/dist/docs/api/types/ContentState.html +70 -0
- package/dist/docs/api/types/FiredEvent.html +43 -0
- package/dist/docs/api/types/ImagereadyKind.html +34 -0
- package/dist/docs/api/types/MediaTypeMatch.html +78 -0
- package/dist/docs/api/types/SwipeDirection.html +36 -0
- package/dist/docs/api/types/TileSourceFactory.html +53 -0
- package/dist/docs/authoring.html +306 -0
- package/dist/docs/events.html +263 -0
- package/dist/docs/iiif.html +738 -69
- package/dist/docs/migration.html +360 -11
- package/dist/docs/readme.html +149 -17
- package/dist/embed/index.html +8 -1
- package/dist/examples/font-css-false.json +33 -0
- package/dist/examples/issue-242-nonlatin.json +55 -0
- package/dist/examples/issue-305-start-at-slide-data.json +96 -0
- package/dist/examples/issue-358-iframe.json +57 -0
- package/dist/examples/issue-iiif-geo.json +32 -0
- package/dist/examples/issue-image-alt.json +23 -0
- package/dist/examples/issue-image-region.json +42 -0
- package/dist/examples/issue-image-srcset.json +24 -0
- package/dist/examples/layers-control.json +60 -0
- package/dist/examples/map-area-overlays.json +68 -0
- package/dist/examples/map-template-provider.json +36 -0
- package/dist/examples/multi-media.json +71 -0
- package/dist/examples/no-map.json +44 -0
- package/dist/examples-iiif/annotated-image.json +204 -0
- package/dist/examples-iiif/bob-boilen.json +11 -5
- package/dist/examples-iiif/courbet.json +9 -6
- package/dist/examples-iiif/empty.json +8 -4
- package/dist/examples-iiif/flickr-drewvigal.json +248 -24
- package/dist/examples-iiif/flickr-zach.wise.json +8 -4
- package/dist/examples-iiif/font-css-false.json +114 -0
- package/dist/examples-iiif/georeferenced-layer-unsupported.json +153 -0
- package/dist/examples-iiif/georeferenced-layer.json +318 -0
- package/dist/examples-iiif/iiif-wellcome.json +9 -6
- package/dist/examples-iiif/instagram-chenpamela.json +381 -36
- package/dist/examples-iiif/instagram_brentfoster.json +429 -40
- package/dist/examples-iiif/instagram_couch.json +261 -26
- package/dist/examples-iiif/instagram_joegermuska.json +441 -41
- package/dist/examples-iiif/instagram_matteich.json +261 -26
- package/dist/examples-iiif/instagram_mirandamulligan.json +261 -26
- package/dist/examples-iiif/instagram_raymondmccrea.json +261 -26
- package/dist/examples-iiif/instagram_test.json +261 -26
- package/dist/examples-iiif/issue-134-no-lines.json +9 -5
- package/dist/examples-iiif/issue-177-color-vars.json +9 -5
- package/dist/examples-iiif/issue-211-rtl.json +9 -5
- package/dist/examples-iiif/issue-242-nonlatin.json +239 -0
- package/dist/examples-iiif/issue-272-facebook.json +9 -5
- package/dist/examples-iiif/issue-305-start-at-slide-data.json +305 -0
- package/dist/examples-iiif/issue-305-start-at-slide.json +9 -5
- package/dist/examples-iiif/issue-341-distance.json +9 -5
- package/dist/examples-iiif/issue-358-iframe.json +190 -0
- package/dist/examples-iiif/issue-360-juxtapose.json +22 -7
- package/dist/examples-iiif/issue-368-null-fields.json +9 -5
- package/dist/examples-iiif/issue-375-code-lists.json +9 -5
- package/dist/examples-iiif/issue-381-dateline.json +9 -5
- package/dist/examples-iiif/issue-405-custom-icon.json +203 -0
- package/dist/examples-iiif/issue-425-many-slides.json +9 -5
- package/dist/examples-iiif/issue-428-background.json +20 -8
- package/dist/examples-iiif/issue-435-googlemaps.json +9 -5
- package/dist/examples-iiif/issue-437-documentcloud.json +22 -7
- package/dist/examples-iiif/issue-451-webp.json +9 -5
- package/dist/examples-iiif/issue-455-audio.json +9 -5
- package/dist/examples-iiif/issue-480-shorts.json +9 -5
- package/dist/examples-iiif/issue-506-marker-sync.json +9 -5
- package/dist/examples-iiif/issue-iiif-geo.json +200 -0
- package/dist/examples-iiif/issue-image-alt.json +90 -0
- package/dist/examples-iiif/issue-image-region.json +203 -0
- package/dist/examples-iiif/issue-image-srcset.json +87 -0
- package/dist/examples-iiif/jansteen.json +9 -6
- package/dist/examples-iiif/katrina.json +197 -41
- package/dist/examples-iiif/layers-control.json +226 -0
- package/dist/examples-iiif/map-area-overlays.json +289 -0
- package/dist/examples-iiif/map-template-provider.json +133 -0
- package/dist/examples-iiif/marktwain.json +255 -58
- package/dist/examples-iiif/multi-media.json +237 -0
- package/dist/examples-iiif/no-map.json +148 -0
- package/dist/examples-iiif/population.json +440 -76
- package/dist/examples-iiif/president.json +456 -83
- package/dist/examples-iiif/scroll-hint.json +190 -0
- package/dist/examples-iiif/seurat.json +113 -22
- package/dist/examples-iiif/zoomify-clamp.json +153 -0
- package/dist/examples-slideshow/single.json +79 -0
- package/dist/examples-slideshow/static/pixel.png +0 -0
- package/dist/examples-slideshow/static.json +44 -0
- package/dist/harness-multi.html +17 -0
- package/dist/harness.html +4 -2
- package/dist/index.html +19 -12
- package/dist/js/storymap.d.ts +2669 -307
- package/dist/js/storymap.js +10820 -6196
- package/dist/js/storymap.js.map +1 -1
- package/dist/navplace-properties.json +54 -0
- package/dist/site.css +54 -36
- package/docs/migration-from-knightlab.md +279 -19
- package/docs/storymap-as-iiif-manifest.md +633 -136
- package/package.json +12 -7
- package/schema/storymap.schema.json +733 -80
- package/src/animation/easings.ts +8 -1
- package/src/animation/tween.ts +186 -0
- package/src/core/Browser.ts +6 -5
- package/src/core/Load.ts +54 -2
- package/src/core/Util.ts +257 -193
- package/src/core/mixins.ts +66 -35
- package/src/core/viewers.ts +94 -0
- package/src/dom/DomEvent.ts +27 -8
- package/src/globals.d.ts +8 -0
- package/src/language/Language.ts +206 -25
- package/src/language/locale/.expected-gaps.json +346 -0
- package/src/language/locale/be.json +15 -2
- package/src/language/locale/bg.json +15 -2
- package/src/language/locale/cs.json +15 -2
- package/src/language/locale/de.json +23 -2
- package/src/language/locale/el.json +15 -2
- package/src/language/locale/en.json +17 -3
- package/src/language/locale/es.json +15 -2
- package/src/language/locale/et.json +5 -1
- package/src/language/locale/fr.json +15 -2
- package/src/language/locale/he.json +16 -3
- package/src/language/locale/hu.json +15 -2
- package/src/language/locale/is.json +5 -1
- package/src/language/locale/it.json +15 -2
- package/src/language/locale/jp.json +15 -2
- package/src/language/locale/ko.json +15 -2
- package/src/language/locale/nl.json +15 -2
- package/src/language/locale/nn.json +5 -1
- package/src/language/locale/no.json +15 -2
- package/src/language/locale/pl.json +15 -2
- package/src/language/locale/pt.json +15 -2
- package/src/language/locale/ru.json +15 -2
- package/src/language/locale/sk.json +15 -2
- package/src/language/locale/sr.json +5 -1
- package/src/language/locale/sv.json +15 -2
- package/src/language/locale/tr.json +15 -2
- package/src/language/locale/uk.json +15 -2
- package/src/language/locale/ur.json +5 -1
- package/src/language/locale/zh-cn.json +15 -2
- package/src/language/locale/zh-tw.json +15 -2
- package/src/main.ts +143 -0
- package/src/map/Map.ts +227 -112
- package/src/map/MapMarker.ts +44 -8
- package/src/map/georeference.ts +325 -0
- package/src/map/openlayers/Map.OpenLayers.ts +1601 -263
- package/src/map/openlayers/MapMarker.OpenLayers.ts +348 -24
- package/src/map/openlayers/zoomifyTiles.ts +1 -4
- package/src/map/slideView.ts +149 -0
- package/src/map/types.ts +2 -0
- package/src/media/EmbedUtil.ts +333 -24
- package/src/media/Media.ts +270 -59
- package/src/media/embedId.ts +110 -0
- package/src/media/types/Audio.ts +9 -82
- package/src/media/types/Blockquote.ts +2 -3
- package/src/media/types/DailyMotion.ts +5 -10
- package/src/media/types/DocumentCloud.ts +6 -35
- package/src/media/types/Facebook.ts +3 -4
- package/src/media/types/Flickr.ts +23 -12
- package/src/media/types/GoogleDoc.ts +3 -4
- package/src/media/types/HtmlMedia.ts +233 -0
- package/src/media/types/IFrame.ts +3 -4
- package/src/media/types/Image.ts +87 -9
- package/src/media/types/Juxtapose.ts +14 -5
- package/src/media/types/SoundCloud.ts +28 -6
- package/src/media/types/Text.ts +46 -21
- package/src/media/types/Twitter.ts +35 -20
- package/src/media/types/Video.ts +9 -79
- package/src/media/types/Vimeo.ts +11 -11
- package/src/media/types/Website.ts +23 -13
- package/src/media/types/Wikipedia.ts +18 -8
- package/src/media/types/YouTube.ts +72 -40
- package/src/scss/Typography.scss +16 -18
- package/src/scss/VCO.StoryMap.scss +208 -12
- package/src/scss/Variables.scss +3 -2
- package/src/scss/core/Mixins.scss +5 -63
- package/src/scss/core/Reset.scss +5 -18
- package/src/scss/fonts/font.amatic-andika.scss +3 -0
- package/src/scss/fonts/font.bitter-raleway.scss +6 -0
- package/src/scss/fonts/font.cairo.scss +79 -0
- package/src/scss/fonts/font.clicker-garamond.scss +5 -0
- package/src/scss/fonts/font.dancing-ledger.scss +1 -0
- package/src/scss/fonts/font.knightlab.scss +14 -0
- package/src/scss/fonts/font.oldstandard.scss +6 -0
- package/src/scss/fonts/font.opensans-gentiumbook.scss +5 -0
- package/src/scss/fonts/font.playfair-faunaone.scss +2 -0
- package/src/scss/fonts/font.playfair.scss +4 -0
- package/src/scss/fonts/font.pt.scss +6 -0
- package/src/scss/fonts/font.ubuntu.scss +4 -0
- package/src/scss/fonts/font.unicaone-vollkorn.scss +5 -0
- package/src/scss/map/VCO.Map.scss +1 -1
- package/src/scss/map/VCO.MapMarker.scss +114 -8
- package/src/scss/map/openlayers/VCO.Map.OpenLayers.scss +7 -4
- package/src/scss/media/VCO.Media.scss +14 -17
- package/src/scss/media/types/VCO.Media.Blockquote.scss +1 -1
- package/src/scss/media/types/VCO.Media.Text.scss +13 -5
- package/src/scss/media/types/VCO.Media.Twitter.scss +4 -4
- package/src/scss/media/types/VCO.Media.Wikipedia.scss +3 -19
- package/src/scss/site/site.scss +55 -36
- package/src/scss/slider/VCO.Slide.scss +61 -13
- package/src/scss/slider/VCO.SlideNav.scss +34 -24
- package/src/scss/slider/VCO.StorySlider.scss +53 -1
- package/src/scss/snapmap/VCO.SnapMap.scss +3 -3
- package/src/scss/ui/VCO.Consent.scss +63 -13
- package/src/scss/ui/VCO.Layers.scss +119 -0
- package/src/scss/ui/VCO.MenuBar.Button.scss +143 -14
- package/src/scss/ui/VCO.MenuBar.scss +9 -2
- package/src/scss/ui/VCO.Message.scss +16 -5
- package/src/site/site.ts +2 -1
- package/src/slider/Slide.ts +326 -74
- package/src/slider/SlideNav.ts +54 -9
- package/src/slider/StorySlider.ts +352 -60
- package/src/storymap/Consent.ts +429 -82
- package/src/storymap/StoryMap.ts +2163 -340
- package/src/storymap/content-state.ts +180 -0
- package/src/storymap/font-css.ts +72 -0
- package/src/storymap/from-slideshow.ts +785 -0
- package/src/storymap/iiif-shared.ts +228 -0
- package/src/storymap/iiif.ts +1742 -95
- package/src/storymap/to-iiif.ts +1008 -0
- package/src/storymap/validate.ts +80 -7
- package/src/types.ts +591 -29
- package/src/ui/LayersControl.ts +302 -0
- package/src/ui/MenuBar.ts +325 -61
- package/src/ui/Message.ts +44 -2
- package/src/ui/Swipable.ts +170 -85
- package/dist/assets/demo-D6cTmzn6.js +0 -1
- package/dist/assets/harness-BPFlzutp.js +0 -1
- package/dist/assets/index-DDsPBPZA.js +0 -1
- package/dist/assets/main-Y-MYUSJ4.js +0 -17
- package/src/animation/morpheus.d.ts +0 -41
- package/src/scss/VCO.StoryMap.Dark.scss +0 -138
- package/src/scss/Variables.Dark.scss +0 -56
- package/src/scss/media/types/VCO.Media.Instagram.scss +0 -7
- package/src/scss/media/types/VCO.Media.Profile.scss +0 -5
- package/src/ui/Draggable.ts +0 -374
|
@@ -2,6 +2,9 @@ import Overlay from "ol/Overlay";
|
|
|
2
2
|
import { fromLonLat } from "ol/proj";
|
|
3
3
|
import type { Map as OlMap } from "ol";
|
|
4
4
|
import MapMarker from "../MapMarker";
|
|
5
|
+
import { clearTimer } from "../../core/Util";
|
|
6
|
+
import { Language } from "../../language/Language";
|
|
7
|
+
import { sanitizeSlideText } from "../../media/EmbedUtil";
|
|
5
8
|
import type { LatLngLiteral, MapMarkerData, StorymapOptions } from "../../types";
|
|
6
9
|
|
|
7
10
|
/* MapMarker.OpenLayers
|
|
@@ -11,30 +14,131 @@ import type { LatLngLiteral, MapMarkerData, StorymapOptions } from "../../types"
|
|
|
11
14
|
existing .vco-mapmarker styles apply unchanged.
|
|
12
15
|
================================================= */
|
|
13
16
|
|
|
17
|
+
/** The resolved marker presentation: `marker.*` over `location.*`. */
|
|
18
|
+
interface MarkerPresentation {
|
|
19
|
+
icon?: string;
|
|
20
|
+
iconSize?: number[];
|
|
21
|
+
image?: string;
|
|
22
|
+
label?: string;
|
|
23
|
+
popup: boolean;
|
|
24
|
+
audioBadge: boolean;
|
|
25
|
+
useCustomMarker?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
14
28
|
export default class OpenLayersMapMarker extends MapMarker {
|
|
15
29
|
declare "_overlay": Overlay;
|
|
30
|
+
declare "_onMarkerClickBound": ((e: Event) => void) | null;
|
|
31
|
+
/** Enter/Space activation for keyboard visitors, detached in dispose(). */
|
|
32
|
+
declare "_onMarkerKeyBound": ((e: KeyboardEvent) => void) | null;
|
|
33
|
+
/** Base accessible name (slide headline or label), without audio suffix. */
|
|
34
|
+
declare "_marker_label": string;
|
|
35
|
+
/** marker.popup: the active marker opens its card when clicked. */
|
|
36
|
+
declare "_popup_enabled": boolean;
|
|
37
|
+
/** marker.audioBadge: flag a slide that has narration or audio media. */
|
|
38
|
+
declare "_audio_badge": boolean;
|
|
39
|
+
/**
|
|
40
|
+
* The popup card while it is open. `declare` fields are undefined until
|
|
41
|
+
* assigned, so every read below is a truthiness check, never `=== null`,
|
|
42
|
+
* and there is no constructor to initialise them: `MapMarker`'s own
|
|
43
|
+
* constructor already runs `_initLayout()` → `_createMarker()`, which
|
|
44
|
+
* assigns the flags, and initialising afterwards would wipe them.
|
|
45
|
+
*/
|
|
46
|
+
declare "_popup_el": HTMLElement | null;
|
|
47
|
+
/** Escape-to-close, detached in dispose(). */
|
|
48
|
+
declare "_onPopupKeyBound": ((e: KeyboardEvent) => void) | null;
|
|
49
|
+
/** Mirrors `active()`, as a flag rather than a method reference. */
|
|
50
|
+
declare "_is_active": boolean;
|
|
16
51
|
|
|
17
52
|
/* Create Marker
|
|
18
53
|
================================================== */
|
|
54
|
+
/**
|
|
55
|
+
* The merged marker presentation: `marker.*` wins, `location.*` is the
|
|
56
|
+
* legacy spelling and still works.
|
|
57
|
+
* `location` also stays the carrier of geography, so lat/lon are read
|
|
58
|
+
* from it alone — presentation never moves a marker.
|
|
59
|
+
*/
|
|
60
|
+
_presentation(d?: MapMarkerData): MarkerPresentation {
|
|
61
|
+
const location = (d?.location ?? {}) as Record<string, unknown>;
|
|
62
|
+
const marker = (d?.marker ?? {}) as Record<string, unknown>;
|
|
63
|
+
return {
|
|
64
|
+
icon: (marker.icon as string | undefined) ?? (location.icon as string | undefined),
|
|
65
|
+
iconSize:
|
|
66
|
+
(marker.iconSize as number[] | undefined) ??
|
|
67
|
+
(location.iconSize as number[] | undefined),
|
|
68
|
+
image: (marker.image as string | undefined) ?? (location.image as string | undefined),
|
|
69
|
+
// in a manifest the label arrives as the navPlace property `name`
|
|
70
|
+
label: (marker.label as string | undefined) ?? (location.name as string | undefined),
|
|
71
|
+
popup: Boolean(marker.popup ?? location.popup),
|
|
72
|
+
audioBadge: Boolean(marker.audioBadge ?? location.audioBadge),
|
|
73
|
+
useCustomMarker: location.use_custom_marker as boolean | undefined,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
19
77
|
_createMarker(d?: MapMarkerData, o?: StorymapOptions): void {
|
|
20
|
-
|
|
78
|
+
const location = d?.location;
|
|
79
|
+
if (location && typeof location.lat == "number" && typeof location.lon == "number") {
|
|
21
80
|
this.data.real_marker = true;
|
|
22
|
-
const
|
|
23
|
-
|
|
81
|
+
const presentation = this._presentation(d);
|
|
82
|
+
this._popup_enabled = presentation.popup;
|
|
83
|
+
this._audio_badge = presentation.audioBadge;
|
|
84
|
+
const use_custom_marker = o?.use_custom_markers || presentation.useCustomMarker;
|
|
85
|
+
if (use_custom_marker && presentation.icon) {
|
|
24
86
|
this._custom_icon = {
|
|
25
|
-
url:
|
|
26
|
-
size:
|
|
27
|
-
anchor: this._customIconAnchor(
|
|
87
|
+
url: presentation.icon,
|
|
88
|
+
size: presentation.iconSize || [48, 48],
|
|
89
|
+
anchor: this._customIconAnchor(presentation.iconSize),
|
|
28
90
|
};
|
|
29
|
-
} else if (use_custom_marker &&
|
|
30
|
-
this._custom_image_icon =
|
|
91
|
+
} else if (use_custom_marker && presentation.image) {
|
|
92
|
+
this._custom_image_icon = presentation.image;
|
|
31
93
|
}
|
|
32
94
|
|
|
33
|
-
this._marker = this._createMarkerElement(d, o);
|
|
34
|
-
|
|
95
|
+
this._marker = this._createMarkerElement(d as MapMarkerData, o);
|
|
96
|
+
// keyboard route to the same behaviour as a click: focusable,
|
|
97
|
+
// announced as a button, Enter/Space activate
|
|
98
|
+
const text = (d as MapMarkerData)?.text;
|
|
99
|
+
const headline =
|
|
100
|
+
typeof text?.headline === "string" && text.headline !== "" ? text.headline : null;
|
|
101
|
+
this._marker_label = presentation.label ?? headline ?? "Marker";
|
|
102
|
+
this._marker.setAttribute("tabindex", "0");
|
|
103
|
+
this._marker.setAttribute("role", "button");
|
|
104
|
+
this._marker.setAttribute("aria-label", this._marker_label);
|
|
105
|
+
// kept as a field so dispose() can detach it; the inline arrow
|
|
106
|
+
// had no other handle
|
|
107
|
+
this._onMarkerClickBound = (e: Event) => {
|
|
35
108
|
e.stopPropagation();
|
|
109
|
+
// the active marker's card toggles instead of navigating:
|
|
110
|
+
// the slide is already showing, and a popup that also moved
|
|
111
|
+
// the story would be impossible to dismiss
|
|
112
|
+
if (this._popup_enabled && this._is_active) {
|
|
113
|
+
this._togglePopup();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
36
116
|
this._onMarkerClick(e);
|
|
37
|
-
}
|
|
117
|
+
};
|
|
118
|
+
this._marker.addEventListener("click", this._onMarkerClickBound);
|
|
119
|
+
// Keyboard visitors get the click behaviour: Enter/Space
|
|
120
|
+
// activates, and an opened card takes focus so its close button
|
|
121
|
+
// is reachable without a pointer.
|
|
122
|
+
this._onMarkerKeyBound = (e: KeyboardEvent) => {
|
|
123
|
+
if (e.key !== "Enter" && e.key !== " " && e.key !== "Spacebar") return;
|
|
124
|
+
e.preventDefault();
|
|
125
|
+
const wasOpen = this.popupOpen;
|
|
126
|
+
this._onMarkerClickBound?.(e);
|
|
127
|
+
if (!wasOpen && this.popupOpen) {
|
|
128
|
+
(
|
|
129
|
+
this._popup_el?.querySelector(
|
|
130
|
+
".vco-marker-popup-close",
|
|
131
|
+
) as HTMLElement | null
|
|
132
|
+
)?.focus();
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
this._marker.addEventListener("keydown", this._onMarkerKeyBound);
|
|
136
|
+
// Escape closes the card; the listener is on the document and is
|
|
137
|
+
// detached in dispose(), so a torn-down map leaves nothing behind
|
|
138
|
+
this._onPopupKeyBound = (e: KeyboardEvent) => {
|
|
139
|
+
if (e.key === "Escape") this._closePopup();
|
|
140
|
+
};
|
|
141
|
+
document.addEventListener("keydown", this._onPopupKeyBound);
|
|
38
142
|
}
|
|
39
143
|
}
|
|
40
144
|
|
|
@@ -49,6 +153,8 @@ export default class OpenLayersMapMarker extends MapMarker {
|
|
|
49
153
|
el.className = "vco-mapmarker-custom";
|
|
50
154
|
const img = document.createElement("img");
|
|
51
155
|
img.src = this._custom_icon.url;
|
|
156
|
+
// decorative: the parent div carries the marker's title
|
|
157
|
+
img.alt = "";
|
|
52
158
|
img.style.width = this._custom_icon.size[0] + "px";
|
|
53
159
|
img.style.height = "auto";
|
|
54
160
|
el.appendChild(img);
|
|
@@ -58,6 +164,7 @@ export default class OpenLayersMapMarker extends MapMarker {
|
|
|
58
164
|
el.className = "vco-mapmarker-image-icon";
|
|
59
165
|
const img = document.createElement("img");
|
|
60
166
|
img.src = this._custom_image_icon;
|
|
167
|
+
img.alt = "";
|
|
61
168
|
img.style.width = "48px";
|
|
62
169
|
img.style.height = "auto";
|
|
63
170
|
el.appendChild(img);
|
|
@@ -69,14 +176,31 @@ export default class OpenLayersMapMarker extends MapMarker {
|
|
|
69
176
|
return el;
|
|
70
177
|
}
|
|
71
178
|
|
|
179
|
+
/**
|
|
180
|
+
* The marker's {lat, lon}, or null when it is a non-georeferenced marker
|
|
181
|
+
* (an overview slide, or a slide with only an image region). `_createMarker`
|
|
182
|
+
* already established this for a real marker, but returning it from one
|
|
183
|
+
* place means the type checker can see it too.
|
|
184
|
+
*/
|
|
185
|
+
latLon(): LatLngLiteral | null {
|
|
186
|
+
const location = this.data.location;
|
|
187
|
+
if (!this.data.real_marker || !location) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
if (typeof location.lat !== "number" || typeof location.lon !== "number") {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
return { lat: location.lat, lon: location.lon };
|
|
194
|
+
}
|
|
195
|
+
|
|
72
196
|
_addTo(m: OlMap): void {
|
|
73
|
-
|
|
74
|
-
|
|
197
|
+
const latlon = this.latLon();
|
|
198
|
+
if (latlon && this._marker) {
|
|
75
199
|
// Image-space maps (IIIF) use EPSG:4326 with raw image pixel coordinates
|
|
76
200
|
const is_image_space = m.getView().getProjection().getCode() === "EPSG:4326";
|
|
77
201
|
const position = is_image_space
|
|
78
|
-
? [
|
|
79
|
-
: fromLonLat([
|
|
202
|
+
? [latlon.lon, latlon.lat]
|
|
203
|
+
: fromLonLat([latlon.lon, latlon.lat]);
|
|
80
204
|
|
|
81
205
|
// Default pins anchor on their tip: OL's inline styles override
|
|
82
206
|
// any CSS top/left, so the alignment is done here — bottom-center
|
|
@@ -101,7 +225,211 @@ export default class OpenLayersMapMarker extends MapMarker {
|
|
|
101
225
|
}
|
|
102
226
|
}
|
|
103
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Terminal teardown: detach the click listener and drop the overlay and
|
|
230
|
+
* element. Called by the map's dispose(); the marker must not be used
|
|
231
|
+
* afterwards.
|
|
232
|
+
*/
|
|
233
|
+
/* Marker popup and audio badge
|
|
234
|
+
================================================== */
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Toggle the popup card for the *active* marker. Inactive markers keep
|
|
238
|
+
* their original behaviour — a click navigates — so a popup never
|
|
239
|
+
* swallows navigation on a map full of pins.
|
|
240
|
+
*
|
|
241
|
+
* The card is a DOM element parented to the marker element and positioned
|
|
242
|
+
* with CSS, which is how it survives a move to the vector renderer
|
|
243
|
+
* (docs/plans/issue-159-vector-markers.md): only the anchor is the
|
|
244
|
+
* marker's, and that is `latLon()`.
|
|
245
|
+
*/
|
|
246
|
+
_togglePopup(): void {
|
|
247
|
+
this.togglePopup();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
_closePopup(): void {
|
|
251
|
+
this.closePopup();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** True when this marker is configured to show a popup card. */
|
|
255
|
+
isPopupEnabled(): boolean {
|
|
256
|
+
return this._popup_enabled === true;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Open the popup card for this marker.
|
|
261
|
+
*
|
|
262
|
+
* Returns false (and opens nothing) when popups are disabled for the
|
|
263
|
+
* marker or it is not a real map marker — e.g. an overview slide. A
|
|
264
|
+
* click on an inactive marker navigates instead of opening (see the
|
|
265
|
+
* click handler in `_createMarker`); programmatic callers that want the
|
|
266
|
+
* card on another stop should navigate first (`StoryMap.openMarkerPopup`
|
|
267
|
+
* does exactly that). Fires `popupopen` on success.
|
|
268
|
+
*/
|
|
269
|
+
openPopup(): boolean {
|
|
270
|
+
if (!this._popup_enabled) return false;
|
|
271
|
+
if (!this.data.real_marker) return false;
|
|
272
|
+
if (this._popup_el) return true;
|
|
273
|
+
const el = this._createPopupElement();
|
|
274
|
+
if (el === null) return false;
|
|
275
|
+
this._marker.appendChild(el);
|
|
276
|
+
this._popup_el = el;
|
|
277
|
+
this._marker.classList.add("vco-mapmarker-popup-open");
|
|
278
|
+
this.fire("popupopen", { marker_number: this.marker_number });
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Close the popup card if it is open. Fires `popupclose`. Also runs on
|
|
284
|
+
* Escape, the card's close button, deactivation and `dispose()`.
|
|
285
|
+
*/
|
|
286
|
+
closePopup(): void {
|
|
287
|
+
if (!this._popup_el) return;
|
|
288
|
+
// keyboard visitors may be inside the card (its close button is
|
|
289
|
+
// focusable): hand focus back to the marker instead of dropping it
|
|
290
|
+
// on the page body. Mouse visitors never had focus here, so theirs
|
|
291
|
+
// is untouched.
|
|
292
|
+
const hadFocus = this._popup_el.contains(document.activeElement);
|
|
293
|
+
this._popup_el.parentNode?.removeChild(this._popup_el);
|
|
294
|
+
this._popup_el = null;
|
|
295
|
+
this._marker?.classList?.remove("vco-mapmarker-popup-open");
|
|
296
|
+
if (hadFocus) this._marker?.focus();
|
|
297
|
+
this.fire("popupclose", { marker_number: this.marker_number });
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Open the card when closed, close it when open. Returns whether the
|
|
302
|
+
* card is open afterwards (false when popups are disabled).
|
|
303
|
+
*/
|
|
304
|
+
togglePopup(): boolean {
|
|
305
|
+
if (this._popup_el) {
|
|
306
|
+
this.closePopup();
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
return this.openPopup();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
get popupOpen(): boolean {
|
|
313
|
+
return Boolean(this._popup_el);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Headline, a sanitized excerpt, the media thumb and an audio control.
|
|
318
|
+
* Every piece of text goes through `sanitizeSlideText` — a slide's text
|
|
319
|
+
* is untrusted input and a marker card is no different (the stored-XSS
|
|
320
|
+
* audit made that lesson once; a label is attacker-controlled text like
|
|
321
|
+
* any other).
|
|
322
|
+
*/
|
|
323
|
+
_createPopupElement(): HTMLElement | null {
|
|
324
|
+
const data = this.data as unknown as {
|
|
325
|
+
text?: { headline?: string; text?: string };
|
|
326
|
+
media?: { thumb?: string | null; url?: string | null; caption?: string | null };
|
|
327
|
+
media_extra?: { thumb?: string | null; url?: string | null; caption?: string | null }[];
|
|
328
|
+
};
|
|
329
|
+
const card = document.createElement("div");
|
|
330
|
+
card.className = "vco-marker-popup";
|
|
331
|
+
// a dialog-ish card, not a modal: no focus trap, Escape closes
|
|
332
|
+
card.setAttribute("role", "group");
|
|
333
|
+
card.setAttribute("aria-label", data.text?.headline ?? "Slide details");
|
|
334
|
+
|
|
335
|
+
const close = document.createElement("button");
|
|
336
|
+
close.type = "button";
|
|
337
|
+
close.className = "vco-marker-popup-close";
|
|
338
|
+
close.setAttribute("aria-label", "Close");
|
|
339
|
+
close.textContent = "\u00d7";
|
|
340
|
+
close.addEventListener("click", (e) => {
|
|
341
|
+
e.stopPropagation();
|
|
342
|
+
this._closePopup();
|
|
343
|
+
});
|
|
344
|
+
card.appendChild(close);
|
|
345
|
+
|
|
346
|
+
// first item with a thumb across the primary and the extras (#358)
|
|
347
|
+
const thumb_item = [data.media, ...(data.media_extra ?? [])].find((item) => !!item?.thumb);
|
|
348
|
+
if (thumb_item?.thumb) {
|
|
349
|
+
const img = document.createElement("img");
|
|
350
|
+
img.className = "vco-marker-popup-thumb";
|
|
351
|
+
img.src = thumb_item.thumb;
|
|
352
|
+
img.alt = thumb_item.caption ?? "";
|
|
353
|
+
img.loading = "lazy";
|
|
354
|
+
card.appendChild(img);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const body = document.createElement("div");
|
|
358
|
+
body.className = "vco-marker-popup-body";
|
|
359
|
+
if (data.text?.headline) {
|
|
360
|
+
const h = document.createElement("h3");
|
|
361
|
+
h.className = "vco-marker-popup-headline";
|
|
362
|
+
h.appendChild(sanitizeSlideText(data.text.headline));
|
|
363
|
+
body.appendChild(h);
|
|
364
|
+
}
|
|
365
|
+
if (data.text?.text) {
|
|
366
|
+
const p = document.createElement("p");
|
|
367
|
+
p.className = "vco-marker-popup-excerpt";
|
|
368
|
+
p.appendChild(sanitizeSlideText(data.text.text));
|
|
369
|
+
body.appendChild(p);
|
|
370
|
+
}
|
|
371
|
+
card.appendChild(body);
|
|
372
|
+
return card;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* A small indicator on markers whose slide has narration or audio media
|
|
377
|
+
* (the Micrio affordance). Recomputed on activation, because a slide's
|
|
378
|
+
* media may only be known once it is resolved.
|
|
379
|
+
*/
|
|
380
|
+
_updateAudioBadge(): void {
|
|
381
|
+
if (!this._audio_badge) return;
|
|
382
|
+
const media = (this.data as unknown as { media?: { mediatype?: { type?: string } } }).media;
|
|
383
|
+
// any audible item badges the marker, not just item 0: an
|
|
384
|
+
// image-first/audio-second slide is still an audible stop (#358)
|
|
385
|
+
const extra =
|
|
386
|
+
(this.data as unknown as { media_extra?: { mediatype?: { type?: string } }[] })
|
|
387
|
+
.media_extra ?? [];
|
|
388
|
+
const audible =
|
|
389
|
+
[media, ...extra].some((item) => {
|
|
390
|
+
const kind = item?.mediatype?.type;
|
|
391
|
+
return kind === "audio" || kind === "video";
|
|
392
|
+
}) || !!this.data.narration;
|
|
393
|
+
this._marker.classList.toggle("vco-mapmarker-has-audio", audible);
|
|
394
|
+
const suffix =
|
|
395
|
+
audible && typeof Language.buttons.audio_badge === "string"
|
|
396
|
+
? `, ${Language.buttons.audio_badge}`
|
|
397
|
+
: "";
|
|
398
|
+
this._marker.setAttribute("aria-label", `${this._marker_label}${suffix}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
dispose(): void {
|
|
402
|
+
const marker = this._marker as unknown as HTMLElement | null;
|
|
403
|
+
this._closePopup();
|
|
404
|
+
if (this._onPopupKeyBound) {
|
|
405
|
+
document.removeEventListener("keydown", this._onPopupKeyBound);
|
|
406
|
+
this._onPopupKeyBound = null;
|
|
407
|
+
}
|
|
408
|
+
if (this._onMarkerClickBound) {
|
|
409
|
+
marker?.removeEventListener?.("click", this._onMarkerClickBound);
|
|
410
|
+
this._onMarkerClickBound = null;
|
|
411
|
+
}
|
|
412
|
+
if (this._onMarkerKeyBound) {
|
|
413
|
+
marker?.removeEventListener?.("keydown", this._onMarkerKeyBound);
|
|
414
|
+
this._onMarkerKeyBound = null;
|
|
415
|
+
}
|
|
416
|
+
if (this._overlay) {
|
|
417
|
+
this._overlay.setElement(undefined);
|
|
418
|
+
this._overlay = null as unknown as Overlay;
|
|
419
|
+
}
|
|
420
|
+
// via the parent so a non-DOM stand-in (a test double) is tolerated
|
|
421
|
+
marker?.parentNode?.removeChild(marker);
|
|
422
|
+
this._marker = null as unknown as HTMLDivElement;
|
|
423
|
+
}
|
|
424
|
+
|
|
104
425
|
_active(a: boolean): void {
|
|
426
|
+
this._is_active = a;
|
|
427
|
+
if (!a) {
|
|
428
|
+
// navigating away closes the card: a popup belongs to its stop
|
|
429
|
+
this._closePopup();
|
|
430
|
+
} else {
|
|
431
|
+
this._updateAudioBadge();
|
|
432
|
+
}
|
|
105
433
|
if (this.data.media && this.data.media.mediatype) {
|
|
106
434
|
this.media_icon_class = "vco-mapmarker-icon vco-icon-" + this.data.media.mediatype.type;
|
|
107
435
|
} else {
|
|
@@ -112,7 +440,7 @@ export default class OpenLayersMapMarker extends MapMarker {
|
|
|
112
440
|
// custom icons look the same active or not (as in the
|
|
113
441
|
// original Leaflet version): only the stacking changes
|
|
114
442
|
if (!a) {
|
|
115
|
-
|
|
443
|
+
clearTimer(this.timer);
|
|
116
444
|
}
|
|
117
445
|
this._marker.style.zIndex = a ? "1000" : "";
|
|
118
446
|
} else if (this._custom_image_icon) {
|
|
@@ -121,7 +449,7 @@ export default class OpenLayersMapMarker extends MapMarker {
|
|
|
121
449
|
this._marker.classList.add("vco-mapmarker-image-icon-active");
|
|
122
450
|
this._marker.style.zIndex = "1000";
|
|
123
451
|
} else {
|
|
124
|
-
|
|
452
|
+
clearTimer(this.timer);
|
|
125
453
|
this._marker.classList.remove("vco-mapmarker-image-icon-active");
|
|
126
454
|
this._marker.classList.add("vco-mapmarker-image-icon");
|
|
127
455
|
this._marker.style.zIndex = "";
|
|
@@ -131,7 +459,7 @@ export default class OpenLayersMapMarker extends MapMarker {
|
|
|
131
459
|
this._marker.classList.add("vco-mapmarker-active");
|
|
132
460
|
this._marker.style.zIndex = "1000";
|
|
133
461
|
} else {
|
|
134
|
-
|
|
462
|
+
clearTimer(this.timer);
|
|
135
463
|
this._marker.classList.remove("vco-mapmarker-active");
|
|
136
464
|
this._marker.classList.add("vco-mapmarker");
|
|
137
465
|
this._marker.style.zIndex = "";
|
|
@@ -166,11 +494,7 @@ export default class OpenLayersMapMarker extends MapMarker {
|
|
|
166
494
|
}
|
|
167
495
|
}
|
|
168
496
|
|
|
169
|
-
_location(): LatLngLiteral {
|
|
170
|
-
|
|
171
|
-
return { lat: this.data.location.lat, lon: this.data.location.lon };
|
|
172
|
-
} else {
|
|
173
|
-
return {} as LatLngLiteral;
|
|
174
|
-
}
|
|
497
|
+
_location(): LatLngLiteral | null {
|
|
498
|
+
return this.latLon();
|
|
175
499
|
}
|
|
176
500
|
}
|
|
@@ -15,10 +15,7 @@ export const ZOOMIFY_TILE_SIZE = 256;
|
|
|
15
15
|
* Full-size tiles are left untouched for OpenLayers' own load handling.
|
|
16
16
|
*/
|
|
17
17
|
export function padCroppedZoomifyTile(tile: ImageTile, image: HTMLImageElement): void {
|
|
18
|
-
if (
|
|
19
|
-
image.naturalWidth !== ZOOMIFY_TILE_SIZE ||
|
|
20
|
-
image.naturalHeight !== ZOOMIFY_TILE_SIZE
|
|
21
|
-
) {
|
|
18
|
+
if (image.naturalWidth !== ZOOMIFY_TILE_SIZE || image.naturalHeight !== ZOOMIFY_TILE_SIZE) {
|
|
22
19
|
const canvas = document.createElement("canvas");
|
|
23
20
|
canvas.width = ZOOMIFY_TILE_SIZE;
|
|
24
21
|
canvas.height = ZOOMIFY_TILE_SIZE;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure view helpers for slideshow-driven slide presentation: rotation,
|
|
3
|
+
* CSS filter grading, spotlight masks and per-slide basemaps.
|
|
4
|
+
*
|
|
5
|
+
* DOM- and OpenLayers-free so the CLI and unit tests can use them; the map
|
|
6
|
+
* engine (`Map.OpenLayers`) applies their output. Every helper treats
|
|
7
|
+
* absent/mistyped input as "feature off", so stories that never mention the
|
|
8
|
+
* new fields render exactly as before.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { StorymapSlideFilter, StorymapSlideLocation, StorymapSlideMask } from "../types";
|
|
12
|
+
import { asClampedNumber } from "../storymap/iiif-shared.ts";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Valid ranges for filter grading, shared by the translator (which reads
|
|
16
|
+
* them) and the renderer (which enforces them again — hand-written JSON
|
|
17
|
+
* reaches the renderer directly, so read-time clamping alone is not enough).
|
|
18
|
+
*/
|
|
19
|
+
export const FILTER_RANGES = {
|
|
20
|
+
brightness: { min: 0, max: 200, def: 100 },
|
|
21
|
+
contrast: { min: 0, max: 200, def: 100 },
|
|
22
|
+
saturate: { min: 0, max: 200, def: 100 },
|
|
23
|
+
hueRotate: { min: -180, max: 180, def: 0 },
|
|
24
|
+
sepia: { min: 0, max: 100, def: 0 },
|
|
25
|
+
blur: { min: 0, max: 20, def: 0 },
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
/** View rotation in radians, or null when the slide states none. */
|
|
29
|
+
export function slideRotationRad(
|
|
30
|
+
location: StorymapSlideLocation | null | undefined,
|
|
31
|
+
): number | null {
|
|
32
|
+
const rotation = location?.rotation;
|
|
33
|
+
if (typeof rotation !== "number" || !Number.isFinite(rotation) || rotation === 0) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return (rotation * Math.PI) / 180;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A `filter()` CSS value for the map viewport from a slide `filter` bag
|
|
41
|
+
* (slideshow `filters`): brightness/contrast/saturate in percent,
|
|
42
|
+
* hue-rotate in degrees, sepia in percent, blur in pixels. Unknown keys
|
|
43
|
+
* never reach CSS. Returns "" when nothing is graded.
|
|
44
|
+
*/
|
|
45
|
+
export function buildSlideFilter(filter: StorymapSlideFilter | null | undefined): string {
|
|
46
|
+
if (!filter || typeof filter !== "object") return "";
|
|
47
|
+
const parts: string[] = [];
|
|
48
|
+
const percent = (value: unknown, name: "brightness" | "contrast" | "saturate" | "sepia") => {
|
|
49
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return;
|
|
50
|
+
const { min, max, def } = FILTER_RANGES[name];
|
|
51
|
+
if (value === def) return;
|
|
52
|
+
parts.push(`${name}(${asClampedNumber(value, min, max)}%)`);
|
|
53
|
+
};
|
|
54
|
+
percent(filter.brightness, "brightness");
|
|
55
|
+
percent(filter.contrast, "contrast");
|
|
56
|
+
percent(filter.saturate, "saturate");
|
|
57
|
+
const hue = asClampedNumber(filter.hueRotate, ...filterRange("hueRotate"));
|
|
58
|
+
if (hue !== null && hue !== FILTER_RANGES.hueRotate.def) {
|
|
59
|
+
parts.push(`hue-rotate(${hue}deg)`);
|
|
60
|
+
}
|
|
61
|
+
percent(filter.sepia, "sepia");
|
|
62
|
+
const blur = asClampedNumber(filter.blur, ...filterRange("blur"));
|
|
63
|
+
if (blur !== null && blur !== FILTER_RANGES.blur.def) {
|
|
64
|
+
parts.push(`blur(${blur}px)`);
|
|
65
|
+
}
|
|
66
|
+
return parts.join(" ");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The `[min, max]` pair for a filter range (spread into `asClampedNumber`). */
|
|
70
|
+
function filterRange(key: keyof typeof FILTER_RANGES): [number, number] {
|
|
71
|
+
return [FILTER_RANGES[key].min, FILTER_RANGES[key].max];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const MASK_COLOR = /^(#[0-9a-f]{3}([0-9a-f]{3}([0-9a-f]{2})?)?|rgba?\([^)]*\))$/i;
|
|
75
|
+
|
|
76
|
+
export interface NormalizedMask {
|
|
77
|
+
x: number;
|
|
78
|
+
y: number;
|
|
79
|
+
w: number;
|
|
80
|
+
h: number;
|
|
81
|
+
color: string;
|
|
82
|
+
invert: boolean;
|
|
83
|
+
opacity: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* A validated spotlight mask (slideshow `passepartout`): normalized
|
|
88
|
+
* viewport fractions plus presentation. Returns null for anything that is
|
|
89
|
+
* not a usable `{x, y, w, h}` shape; out-of-range fractions are clamped,
|
|
90
|
+
* an unparsable color falls back to translucent black.
|
|
91
|
+
*/
|
|
92
|
+
export function normalizeMask(mask: StorymapSlideMask | null | undefined): NormalizedMask | null {
|
|
93
|
+
if (!mask || typeof mask !== "object") return null;
|
|
94
|
+
const { x, y, w, h } = mask;
|
|
95
|
+
if (
|
|
96
|
+
typeof x !== "number" ||
|
|
97
|
+
typeof y !== "number" ||
|
|
98
|
+
typeof w !== "number" ||
|
|
99
|
+
typeof h !== "number" ||
|
|
100
|
+
![x, y, w, h].every(Number.isFinite)
|
|
101
|
+
) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
const clamp01 = (n: number) => Math.min(1, Math.max(0, n));
|
|
105
|
+
const nx = clamp01(x);
|
|
106
|
+
const ny = clamp01(y);
|
|
107
|
+
// shrink the far edge with a clamped origin (like clampRegion): a mask
|
|
108
|
+
// fully outside the viewport resolves to nothing, not a full mask
|
|
109
|
+
const nw = Math.min(1 - nx, Math.max(0, w + Math.min(0, x)));
|
|
110
|
+
const nh = Math.min(1 - ny, Math.max(0, h + Math.min(0, y)));
|
|
111
|
+
if (!(nw > 0) || !(nh > 0)) return null;
|
|
112
|
+
const rawColor = typeof mask.color === "string" ? mask.color.trim() : "";
|
|
113
|
+
const color = rawColor !== "" && MASK_COLOR.test(rawColor) ? rawColor : "rgba(0,0,0,0.5)";
|
|
114
|
+
const opacity =
|
|
115
|
+
typeof mask.opacity === "number" && Number.isFinite(mask.opacity)
|
|
116
|
+
? Math.min(1, Math.max(0, mask.opacity))
|
|
117
|
+
: 1;
|
|
118
|
+
return { x: nx, y: ny, w: nw, h: nh, color, invert: mask.invert === true, opacity };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** The per-slide basemap key, or null when the slide keeps the story basemap. */
|
|
122
|
+
export function slideBasemapKey(location: StorymapSlideLocation | null | undefined): string | null {
|
|
123
|
+
const basemap = location?.basemap;
|
|
124
|
+
return typeof basemap === "string" && basemap !== "" ? basemap : null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* True for plain image-file URLs (no IIIF service, `{z}` template or
|
|
129
|
+
* keyword). Only unambiguous raster extensions: `.jpx`/`.jp2` can be IIIF
|
|
130
|
+
* bases too (Leipzig serves `info.json` off `.jpx`), so those go through
|
|
131
|
+
* the service attempt with a static fallback instead. Relative URLs
|
|
132
|
+
* (same-origin static files) count; templates never match (no extension).
|
|
133
|
+
*/
|
|
134
|
+
export function isStaticImageUrl(url: string): boolean {
|
|
135
|
+
if (url.includes("{z}")) return false;
|
|
136
|
+
if (!/\.(jpe?g|png|gif|webp|tif?f|bmp|avif)(\?.*)?$/i.test(url)) return false;
|
|
137
|
+
if (/^https?:\/\//i.test(url)) return true;
|
|
138
|
+
// same-origin relative file without any other scheme (mapbox:, osm:, …)
|
|
139
|
+
return !/^[a-z][a-z0-9+.-]*:/i.test(url);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* An image service base as its `info.json` URL (idempotent for URLs that
|
|
144
|
+
* already name it). Shared by the slideshow translator (story default) and
|
|
145
|
+
* the per-slide basemap builder so the suffix rule cannot drift.
|
|
146
|
+
*/
|
|
147
|
+
export function serviceToInfoJson(service: string): string {
|
|
148
|
+
return /info\.json$/i.test(service) ? service : `${service.replace(/\/$/, "")}/info.json`;
|
|
149
|
+
}
|
package/src/map/types.ts
CHANGED
|
@@ -7,6 +7,8 @@ export interface ViewToOptions {
|
|
|
7
7
|
calculate_zoom?: boolean;
|
|
8
8
|
duration?: number;
|
|
9
9
|
zoom?: number;
|
|
10
|
+
/** View rotation in degrees clockwise; absent leaves rotation unchanged. */
|
|
11
|
+
rotation?: number;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
/** A point of the connection line: either a slide or a raw lat/lon pair. */
|