+import PropTypes from 'prop-types';
import React from 'react';
-import { Button } from 'react-bootstrap';
+import { Button, Form } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { useOpenSeadragon } from './OpenSeadragon';
-const Buttons = () => {
+const Buttons = ({ setUWOverlay, uwOverlay }) => {
const { activeMap, setActiveMap } = useOpenSeadragon();
const { t } = useTranslation();
- return <div className="button-bar">
- {['lw', 'dw', 'sp', 'uw'].map(map =>
- <Button
- active={activeMap === map}
- key={map}
- onClick={() => setActiveMap(map)}
- title={t(`map.${map}Long`)}
- variant="outline-secondary"
- >
- {t(`map.${map}Short`)}
- </Button>
- )}
+ return <div className="mt-5">
+ <div className="button-bar">
+ {['lw', 'dw', 'sp', 'uw'].map(map =>
+ <Button
+ active={activeMap === map}
+ key={map}
+ onClick={() => setActiveMap(map)}
+ title={t(`map.${map}Long`)}
+ variant="outline-secondary"
+ >
+ {t(`map.${map}Short`)}
+ </Button>
+ )}
+ </div>
+ {activeMap === 'uw' ?
+ <div className="mt-2">
+ <Form.Check
+ checked={uwOverlay}
+ id="toggle-uw-overlay"
+ inline
+ onChange={e => setUWOverlay(e.target.checked)}
+ type="checkbox"
+ />
+ <Form.Label className="mt-0" htmlFor="toggle-uw-overlay">
+ {t('map.uwOverlay')}
+ </Form.Label>
+ </div>
+ : null}
</div>;
};
+Buttons.propTypes = {
+ setUWOverlay: PropTypes.func,
+ uwOverlay: PropTypes.bool,
+};
+
export default Buttons;
import { useOpenSeadragon } from './OpenSeadragon';
-const Overlay = ({ children, onClick, x, y }) => {
+const Overlay = ({ children, height, onClick, page, width, x, y }) => {
const { viewer } = useOpenSeadragon();
const [element] = React.useState(document.createElement('div'));
React.useEffect(() => {
if (!viewer) return;
- viewer.addOverlay(
- element,
- new OpenSeadragon.Point(x, y),
- OpenSeadragon.Placement.CENTER,
- );
- if (onClick) {
- new OpenSeadragon.MouseTracker({
- element,
- clickHandler: onClick,
- });
+ const add = () => {
+ if (width && height) {
+ viewer.addOverlay(
+ element,
+ new OpenSeadragon.Rect(x, y, width, height),
+ );
+ } else {
+ viewer.addOverlay(
+ element,
+ new OpenSeadragon.Point(x, y),
+ OpenSeadragon.Placement.CENTER,
+ );
+ }
+ if (onClick) {
+ new OpenSeadragon.MouseTracker({
+ element,
+ clickHandler: onClick,
+ });
+ }
+ };
+ const addPage = () => {
+ if (viewer.currentPage() === page) {
+ add();
+ }
+ };
+ if (typeof page !== 'undefined') {
+ viewer.addHandler('page', addPage);
+ return () => {
+ viewer.removeHandler('page', addPage);
+ viewer.removeOverlay(element);
+ };
}
- }, [onClick, viewer, x, y]);
+ if (viewer.isOpen()) {
+ add();
+ } else {
+ viewer.addHandler('open', add);
+ return () => {
+ viewer.removeHandler('open', add);
+ viewer.removeOverlay(element);
+ };
+ }
+ }, [onClick, height, page, viewer, width, x, y]);
return createPortal(children, element);
};
Overlay.propTypes = {
children: PropTypes.node,
+ height: PropTypes.number,
onClick: PropTypes.func,
+ page: PropTypes.number,
+ width: PropTypes.number,
x: PropTypes.number,
y: PropTypes.number,
};
--- /dev/null
+import OpenSeadragon from 'openseadragon';
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import Overlay from './Overlay';
+import { useOpenSeadragon } from './OpenSeadragon';
+
+const dropMap = {
+ '00': '10',
+ '03': '02',
+ '05': '05',
+ '06': '05',
+ '07': '17',
+ '08': '07',
+ '09': '4B',
+ '0A': '09',
+ '0B': '6A',
+ '0D': '0B',
+ '0F': '01',
+ '10': '01',
+ '12': '0D',
+ '13': '0D',
+ '14': '0D',
+ '17': '27',
+ '18': '12',
+ '19': '07',
+ '1E': '3E',
+ '20': '01',
+ '21': '0D',
+ '22': '0D',
+ '23': '0D',
+ '24': '08',
+ '27': '31',
+ '29': '07',
+ '2A': '07',
+ '2C': '12',
+ '2D': '06',
+ '2E': '06',
+ '2F': '02',
+ '31': '77',
+ '33': '08',
+ '35': '08',
+ '36': '08',
+ '37': '08',
+ '39': '29',
+ '3A': '0A',
+ '3C': '0E',
+ '3D': '96',
+ '43': '0A',
+ '44': '0A',
+ '46': '09',
+ '47': '07',
+ '48': '07',
+ '49': '07',
+ '4B': '09',
+ '4D': 'A6',
+ '4F': 'BE',
+ '54': '34',
+ '55': '09',
+ '56': '09',
+ '57': '09',
+ '58': '09',
+ '59': '07',
+ '5A': '0E',
+ '5B': '0E',
+ '5E': '7E',
+ '65': 'AC',
+ '67': '09',
+ '68': '07',
+ '6D': '0B',
+ '73': '05',
+ '74': '05',
+ '75': '08',
+ '77': 'A7',
+ '78': '9D',
+ '79': '9D',
+ '7A': '9D',
+ '7B': '9D',
+ '7C': '0E',
+ '7D': '9B',
+ '7E': '9E',
+ '81': '05',
+ '82': '05',
+ '83': '05',
+ '84': '05',
+ '85': '05',
+ '88': 'A9',
+ '89': 'A9',
+ '8A': '0E',
+ '8B': '0E',
+ '8C': '1C',
+ '8D': '0B',
+ '8F': '0C',
+ '90': '0C',
+ '92': '0C',
+ '94': '0E',
+ '95': '0E',
+ '97': 'D1',
+ '9A': '7D',
+ '9B': '7D',
+ '9C': '0E',
+ '9D': '7B',
+ '9E': 'BE',
+ '9F': '0C',
+ 'A1': '0C',
+ 'A3': '0C',
+ 'A4': '0E',
+ 'A7': '17',
+ 'A8': '05',
+ 'A9': '89',
+ 'AA': '0A',
+ 'AC': '0B',
+ 'AF': '02',
+ 'B1': 'B2',
+ 'B2': '0C',
+ 'B3': '0D',
+ 'B7': '0D',
+ 'B8': '05',
+ 'B9': '05',
+ 'BA': '0A',
+ 'BB': '0A',
+ 'BD': '4F',
+ 'BE': '4F',
+ 'BF': '02',
+ 'C1': '0C',
+ 'C2': '0C',
+ 'C3': '0D',
+ 'C5': '0D',
+ 'C6': '0D',
+ 'C7': '05',
+ 'C8': '05',
+ 'C9': '0A',
+ 'CA': '0B',
+ 'CB': '0B',
+ 'CC': '0B',
+ 'CD': 'DE',
+ 'CE': 'DE',
+ 'D1': 'B1',
+ 'D3': '05',
+ 'D4': '05',
+ 'D5': '0D',
+ 'D6': '0D',
+ 'D7': '05',
+ 'D8': '05',
+ 'D9': '05',
+ 'DB': '0B',
+ 'DC': '0B',
+ 'DD': '06',
+ 'DE': '06',
+ 'E1': '06',
+ 'E2': '06',
+ 'E3': '14',
+ 'E4': '06',
+ 'E5': '06',
+ 'E6': '06',
+ 'E7': '06',
+ 'E8': 'F8',
+ 'E9': 'FA',
+ 'EA': 'FA',
+ 'EB': 'FB',
+ 'EC': 'FD',
+ 'ED': 'FD',
+ 'EE': 'FE',
+ 'EF': 'FF',
+ 'F0': '06',
+ 'F1': '06',
+ 'F4': '06',
+ 'F5': '06',
+ 'F9': '06',
+ 'FE': '06',
+};
+
+const strongEG = [
+ '08',
+ '0C',
+ '15',
+ '2F',
+ '40',
+ '51',
+ '52',
+ '59',
+ '5B',
+ '60',
+ '62',
+ '66',
+ '71',
+ '72',
+ '81',
+ 'A2',
+ 'A8',
+ 'A9',
+ 'AA',
+ 'B2',
+ 'B3',
+ 'B9',
+ 'C2',
+ 'C3',
+ 'CB',
+ 'CC',
+ 'DB',
+ 'DC',
+ 'DF',
+ 'E1',
+ 'E3',
+ 'FA',
+];
+
+const weakEG = [
+ '07',
+ '0A',
+ '16',
+ '28',
+ '2A',
+ '2B',
+ '34',
+ '35',
+ '36',
+ '37',
+ '3A',
+ '4D',
+ '55',
+ '61',
+ '76',
+ '99',
+ 'A0',
+ 'C9',
+ 'E2',
+ 'E4',
+ 'F0',
+ 'FD',
+ 'FE',
+];
+
+const kick = [
+ '1B',
+ '29',
+ '3E',
+ '43',
+ '97',
+];
+
+const dark = [
+ '0B',
+ '19',
+ '21',
+ '22',
+ '32',
+ '41',
+ '42',
+ '69',
+ '6A',
+ '92',
+ '93',
+ 'B5',
+ 'BA',
+ 'C0',
+ 'D0',
+ 'E5',
+ 'E6',
+ 'E7',
+ 'F0',
+ 'F1',
+];
+
+const camera = {
+ '00': ['x', 'y'],
+ '01': ['y'],
+ '02': ['y'],
+ '07': ['x', 'y'],
+ '0A': ['x', 'y'],
+ '0C': ['x', 'y'],
+ '0D': ['x', 'y'],
+ '0E': ['yu'],
+ '10': ['x'],
+ '11': ['x'],
+ '12': ['x', 'y'],
+ '13': ['x'],
+ '14': ['x', 'y'],
+ '15': ['x', 'y'],
+ '16': ['y'],
+ '17': ['x', 'y'],
+ '18': ['x'],
+ '19': ['xr'],
+ '1A': ['xl'],
+ '1B': ['yu'],
+ '1D': ['y'],
+ '1F': ['yu'],
+ '20': ['x', 'y'],
+ '21': ['y'],
+ '22': ['y'],
+ '26': ['yd'],
+ '27': ['x', 'y'],
+ '28': ['x', 'y'],
+ '2A': ['x', 'y'],
+ '2B': ['xl'],
+ '2F': ['yd'],
+ '31': ['yu'],
+ '32': ['x', 'y'],
+ '34': ['x', 'y'],
+ '35': ['y'],
+ '36': ['x', 'y'],
+ '37': ['y'],
+ '38': ['x'],
+ '39': ['yu'],
+ '3A': ['x', 'y'],
+ '3B': ['x'],
+ '3C': ['x', 'y'],
+ '3E': ['yd'],
+ '3F': ['yu'],
+ '40': ['xl'],
+ '41': ['x', 'y'],
+ '42': ['y'],
+ '43': ['yu'],
+ '44': ['xr'],
+ '45': ['xr'],
+ '46': ['y'],
+ '49': ['xr'],
+ '4A': ['y'],
+ '4B': ['yd'],
+ '4C': ['x'],
+ '4D': ['x', 'y'],
+ '4E': ['yd'],
+ '50': ['x'],
+ '51': ['x', 'y'],
+ '52': ['xl', 'yd'],
+ '53': ['xr'],
+ '54': ['x', 'y'],
+ '55': ['y'],
+ '56': ['xr'],
+ '58': ['xr'],
+ '59': ['x'],
+ '5B': ['x'],
+ '5C': ['yu'],
+ '60': ['x'],
+ '61': ['x', 'y'],
+ '62': ['x', 'y'],
+ '63': ['xr'],
+ '64': ['yu'],
+ '65': ['yu'],
+ '66': ['yd'],
+ '67': ['x'],
+ '68': ['x', 'y'],
+ '6A': ['x'],
+ '6B': ['yu'],
+ '6D': ['xr'],
+ '72': ['y'],
+ '74': ['y'],
+ '75': ['xr'],
+ '76': ['xl'],
+ '77': ['x', 'y'],
+ '7B': ['yu'],
+ '7C': ['x'],
+ '7D': ['yu'],
+ '7E': ['xr'],
+ '7F': ['xr'],
+ '80': ['y'],
+ '81': ['x', 'y'],
+ '82': ['x', 'y'],
+ '83': ['xr'],
+ '84': ['x', 'y'],
+ '85': ['xl'],
+ '89': ['y'],
+ '8B': ['xl'],
+ '8D': ['xr'],
+ '91': ['x'],
+ '92': ['xr'],
+ '93': ['yu'],
+ '95': ['x'],
+ '96': ['xl'],
+ '97': ['xr'],
+ '98': ['yd'],
+ '99': ['yd'],
+ '9B': ['yd'],
+ '9C': ['x', 'y'],
+ '9D': ['yd'],
+ 'A0': ['y'],
+ 'A1': ['xr', 'yu'],
+ 'A2': ['x', 'y'],
+ 'A3': ['x'],
+ 'A5': ['yd'],
+ 'A6': ['x', 'y'],
+ 'A7': ['yd'],
+ 'A8': ['xr'],
+ 'A9': ['x', 'y'],
+ 'AA': ['xl'],
+ 'B1': ['xr'],
+ 'B2': ['yu'],
+ 'B3': ['x'],
+ 'B4': ['x', 'y'],
+ 'B5': ['x', 'y'],
+ 'B7': ['x'],
+ 'B8': ['x'],
+ 'B9': ['x', 'y'],
+ 'BB': ['xl'],
+ 'BC': ['xr'],
+ 'BE': ['xl'],
+ 'C0': ['xl'],
+ 'C2': ['x', 'y'],
+ 'C3': ['x'],
+ 'C4': ['x', 'y'],
+ 'C5': ['x'],
+ 'C6': ['x', 'y'],
+ 'C7': ['x', 'y'],
+ 'C9': ['y'],
+ 'CB': ['x', 'y'],
+ 'CC': ['x', 'y'],
+ 'D0': ['xl'],
+ 'D2': ['xr'],
+ 'D5': ['x'],
+ 'D6': ['x'],
+ 'D8': ['xl'],
+ 'D9': ['yu'],
+ 'DA': ['yu'],
+ 'DB': ['x', 'y'],
+ 'DC': ['x', 'y'],
+ 'DF': ['yd'],
+ 'E1': ['x'],
+ 'E2': ['x'],
+ 'E4': ['x'],
+ 'E5': ['x', 'y'],
+ 'E6': ['xr', 'yd'],
+ 'E7': ['xr', 'yu'],
+ 'E8': ['x', 'yu'],
+ 'EA': ['xl', 'yu'],
+ 'EB': ['x'],
+ 'ED': ['x', 'yu'],
+ 'EE': ['x', 'y'],
+ 'EF': ['yd'],
+ 'F0': ['x', 'y'],
+ 'F1': ['x', 'y'],
+ 'F8': ['x', 'y'],
+ 'F9': ['xr', 'yd'],
+ 'FA': ['x', 'y'],
+ 'FB': ['xr', 'yd'],
+ 'FD': ['x', 'y'],
+ 'FE': ['xr'],
+ 'FF': ['yd'],
+};
+
+const getClassName = key => {
+ const classNames = [];
+ if (strongEG.includes(key)) {
+ classNames.push('strong-eg');
+ }
+ if (weakEG.includes(key)) {
+ classNames.push('weak-eg');
+ }
+ if (kick.includes(key)) {
+ classNames.push('kick');
+ }
+ if (dark.includes(key)) {
+ classNames.push('dark');
+ }
+ if (camera[key]) {
+ camera[key].forEach(c => {
+ classNames.push(`cam-${c}`);
+ });
+ }
+ return classNames.join(' ');
+};
+
+const UWSuperTiles = ({ show }) => {
+ const { viewer } = useOpenSeadragon();
+
+ const onClick = React.useCallback(e => {
+ if (e.originalTarget.tagName !== 'A') return;
+ if (e.originalTarget.className !== 'cell-link') return;
+ const key = e.originalTarget.dataset.key;
+
+ const x = (parseInt(key[1], 16) + 0.5) / 16;
+ const y = (parseInt(key[0], 16) + 0.5) / 16;
+ if (viewer && viewer.viewport) {
+ viewer.viewport.panTo(new OpenSeadragon.Point(x, y));
+ viewer.viewport.zoomTo(4);
+ viewer.element.scrollIntoView();
+ }
+ }, [viewer]);
+
+ return <Overlay onClick={onClick} page={3} x={0} y={0} width={1} height={1}>
+ <div className={`uw-super-tiles ${show ? '' : 'd-none'}`}>
+ {[...Array(16).keys()].map(x =>
+ [...Array(16).keys()].map(y => {
+ const key = `${x.toString(16).toUpperCase()}${y.toString(16).toUpperCase()}`;
+ return <div className={getClassName(key)} key={key}>
+ <p className="cell-id">{key}</p>
+ {dropMap[key] ?
+ <p className="cell-drop">
+ <a className="cell-link" data-key={dropMap[key]}>
+ {`▶ ${dropMap[key]}`}
+ </a>
+ </p>
+ : null}
+ </div>;
+ })
+ )}
+ </div>
+ </Overlay>;
+};
+
+UWSuperTiles.propTypes = {
+ show: PropTypes.bool,
+};
+
+export default UWSuperTiles;
import List from '../map/List';
import OpenSeadragon from '../map/OpenSeadragon';
import Pins from '../map/Pins';
+import UWSuperTiles from '../map/UWSuperTiles';
const Map = () => {
+ const [uwOverlay, setUWOverlay] = React.useState(false);
+
const container = React.useRef();
const { t } = useTranslation();
<meta name="description" content={t('map.description')} />
</Helmet>
<OpenSeadragon ref={container}>
- <div className="d-flex align-items-center justify-content-between">
+ <div className="d-flex align-items-start justify-content-between">
<h1>{t('map.heading')}</h1>
- <Buttons />
+ <Buttons setUWOverlay={setUWOverlay} uwOverlay={uwOverlay} />
</div>
<div ref={container} style={{ height: '80vh' }} />
<Pins />
+ <UWSuperTiles show={uwOverlay} />
<List />
</OpenSeadragon>
</Container>;
spLong: 'Spezielle Gebiete',
spShort: 'SP',
uwLong: 'Underworld',
+ uwOverlay: 'Gitter zigen',
uwShort: 'UW',
},
menu: {
spLong: 'Special Areas',
spShort: 'SP',
uwLong: 'Underworld',
+ uwOverlay: 'Show grid',
uwShort: 'UW',
},
menu: {
text-decoration: none;
}
}
+
+.uw-super-tiles {
+ display: grid;
+ height: 100%;
+ grid-template-columns: repeat(16, 1fr);
+
+ > div {
+ position: relative;
+ border: thin solid black;
+ padding: .25ex;
+
+ &.dark {
+ border: medium solid black;
+ }
+ &.kick {
+ border: medium solid yellow;
+ }
+ &.weak-eg {
+ border: medium solid orange;
+ }
+ &.strong-eg {
+ border: medium solid green;
+ }
+
+ &::before,
+ &::after {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ }
+
+ &.cam-x::before {
+ content: '';
+ height: 50%;
+ border-bottom: thin solid cyan;
+ }
+ &.cam-xl::before {
+ content: '';
+ position: absolute;
+ width: 50%;
+ height: 50%;
+ border-bottom: thin solid cyan;
+ }
+ &.cam-xr::before {
+ content: '';
+ position: absolute;
+ left: 50%;
+ width: 50%;
+ height: 50%;
+ border-bottom: thin solid cyan;
+ }
+ &.cam-y::after {
+ content: '';
+ position: absolute;
+ width: 50%;
+ border-right: thin solid cyan;
+ }
+ &.cam-yu::after {
+ content: '';
+ position: absolute;
+ width: 50%;
+ height: 50%;
+ border-right: thin solid cyan;
+ }
+ &.cam-yd::after {
+ content: '';
+ position: absolute;
+ top: 50%;
+ width: 50%;
+ height: 50%;
+ border-right: thin solid cyan;
+ }
+ }
+
+ .cell-id,
+ .cell-drop {
+ margin: 0;
+ line-height: 1;
+ }
+ .cell-drop {
+ font-size: 80%;
+ }
+ .cell-link {
+ color: inherit;
+ cursor: pointer;
+ text-decoration: none;
+ }
+}